From 658f15d0864d52eb1020985d5c82be4947f96c2c12076a6854a84bfe88b71d48 Mon Sep 17 00:00:00 2001 From: Nils Blomgren Date: Thu, 4 Jun 2026 08:51:22 +0200 Subject: [PATCH] first commit ish --- .dockerignore | 2 + Dockerfile | 29 + .../HasTenantAccessRequirement.cs | 32 + .../Components/Layout/NavMenu.razor | 47 +- .../Components/Layout/NavMenu.razor.css | 8 + .../Components/Pages/Admin/AdminBackup.razor | 141 + src/EntKube.Web/Components/Pages/Home.razor | 15 +- .../Pages/Portal/AppGovernancePanel.razor | 201 + .../Portal/CustomerIncidentHistory.razor | 165 + .../Portal/CustomerMaintenanceNotice.razor | 54 + .../Pages/Portal/CustomerPortal.razor | 381 +- .../Pages/Portal/CustomerSlaReport.razor | 123 + .../Pages/Portal/CustomerStatusOverview.razor | 281 ++ .../Pages/Portal/PortalDeploymentDetail.razor | 946 +++- .../Portal/PortalDeploymentDetail.razor.js | 4 + .../Pages/Portal/PortalIdentityDetail.razor | 506 ++ .../Pages/Portal/PortalRegistryDetail.razor | 524 ++ .../Pages/Portal/PortalSecretsDetail.razor | 500 ++ .../Pages/Shared/DockerRegistriesPanel.razor | 615 +++ .../Pages/Shared/ResourceTreePanel.razor | 570 +++ .../Pages/Tenants/AlertRulesPanel.razor | 195 + .../Components/Pages/Tenants/AppDetail.razor | 1076 +++- .../Pages/Tenants/AppDetail.razor.js | 4 + .../Pages/Tenants/AppResourcesPanel.razor | 334 ++ .../Components/Pages/Tenants/CacheTab.razor | 689 +++ .../Pages/Tenants/ClusterDetail.razor | 395 +- .../Pages/Tenants/ClusterHealthPanel.razor | 8 +- .../Pages/Tenants/ClusterMonitoring.razor | 88 +- .../Pages/Tenants/CustomerTab.razor | 106 +- .../Tenants/DatabaseMonitoringPanel.razor | 103 + .../Pages/Tenants/DatabaseTab.razor | 3574 ++++++++++++-- .../Pages/Tenants/GitReposTab.razor | 463 ++ .../Pages/Tenants/GovernanceTab.razor | 585 +++ .../Pages/Tenants/IdentityTab.razor | 902 +++- .../Pages/Tenants/IncidentManagement.razor | 313 ++ .../Components/Pages/Tenants/LogBrowser.razor | 222 + .../Pages/Tenants/MaintenanceWindows.razor | 213 + .../Pages/Tenants/MessagingTab.razor | 1509 ++++++ .../Pages/Tenants/NetworkingTab.razor | 1284 +++++ .../Pages/Tenants/NotificationChannels.razor | 354 ++ .../Tenants/RabbitMQMonitoringPanel.razor | 102 + .../Pages/Tenants/RegistryTab.razor | 1062 ++++ .../Pages/Tenants/ScrapeTargetsPanel.razor | 155 + .../Pages/Tenants/SlaTargetManager.razor | 251 + .../Pages/Tenants/StorageBrowser.razor | 632 +++ .../Pages/Tenants/StorageBrowser.razor.js | 20 + .../Components/Pages/Tenants/StorageTab.razor | 302 +- .../Pages/Tenants/TenantDetail.razor | 106 + .../Components/Pages/Tenants/TenantList.razor | 196 +- .../Tenants/TenantMonitoringOverview.razor | 200 + .../Components/Pages/Tenants/VaultTab.razor | 149 +- src/EntKube.Web/Components/_Imports.razor | 1 + src/EntKube.Web/Data/AlertIncident.cs | 27 + src/EntKube.Web/Data/App.cs | 10 + src/EntKube.Web/Data/AppDeployment.cs | 45 + src/EntKube.Web/Data/AppNetworkPolicy.cs | 44 + src/EntKube.Web/Data/AppQuota.cs | 29 + src/EntKube.Web/Data/AppRbacPolicy.cs | 52 + src/EntKube.Web/Data/ApplicationDbContext.cs | 694 ++- src/EntKube.Web/Data/AuditEvent.cs | 37 + src/EntKube.Web/Data/CacheBinding.cs | 33 + src/EntKube.Web/Data/CnpgCluster.cs | 13 + src/EntKube.Web/Data/DatabaseBinding.cs | 7 + src/EntKube.Web/Data/DeploymentEnums.cs | 21 +- .../Data/DeploymentHealthSnapshot.cs | 14 + src/EntKube.Web/Data/DeploymentManifest.cs | 6 + .../Data/DockerRegistryCredential.cs | 68 + src/EntKube.Web/Data/DockerRegistryType.cs | 16 + src/EntKube.Web/Data/ExternalRoute.cs | 11 + .../Data/ExternalRouteHealthHistory.cs | 13 + src/EntKube.Web/Data/GitAuthType.cs | 9 + src/EntKube.Web/Data/GitKnownHost.cs | 37 + src/EntKube.Web/Data/GitRepository.cs | 52 + src/EntKube.Web/Data/HarborComponentConfig.cs | 40 + src/EntKube.Web/Data/HarborProject.cs | 34 + src/EntKube.Web/Data/IncidentNote.cs | 12 + src/EntKube.Web/Data/KeycloakBackup.cs | 4 +- .../Data/KeycloakComponentConfig.cs | 38 +- src/EntKube.Web/Data/KeycloakRealm.cs | 8 + src/EntKube.Web/Data/KeycloakTheme.cs | 31 + src/EntKube.Web/Data/MaintenanceWindow.cs | 17 + src/EntKube.Web/Data/MessagingBinding.cs | 45 + ...0521201858_AddExternalKeycloak.Designer.cs | 2224 +++++++++ .../20260521201858_AddExternalKeycloak.cs | 48 + ...0260522065948_AddKeycloakTheme.Designer.cs | 2288 +++++++++ .../20260522065948_AddKeycloakTheme.cs | 82 + ...22091248_AddRegisteredPostgres.Designer.cs | 2454 ++++++++++ .../20260522091248_AddRegisteredPostgres.cs | 190 + ...0451_AddRegisteredPostgresDump.Designer.cs | 2505 ++++++++++ ...0260522110451_AddRegisteredPostgresDump.cs | 60 + ...4104_AddCnpgClusterVaultSecret.Designer.cs | 2517 ++++++++++ ...0260524194104_AddCnpgClusterVaultSecret.cs | 50 + ...cloakBackupStorageLinkNullable.Designer.cs | 2516 ++++++++++ ...03011_KeycloakBackupStorageLinkNullable.cs | 61 + ...50057_AddHarborComponentConfig.Designer.cs | 2592 ++++++++++ ...20260526050057_AddHarborComponentConfig.cs | 84 + ...260526113535_AddCnpgMaxBackups.Designer.cs | 2596 ++++++++++ .../20260526113535_AddCnpgMaxBackups.cs | 29 + ...14839_FixCnpgMaxBackupsDefault.Designer.cs | 2595 ++++++++++ ...20260526114839_FixCnpgMaxBackupsDefault.cs | 22 + ...60526121548_AddMongoMaxBackups.Designer.cs | 2599 ++++++++++ .../20260526121548_AddMongoMaxBackups.cs | 29 + ...1835_FixMongoMaxBackupsDefault.Designer.cs | 2602 ++++++++++ ...0260526121835_FixMongoMaxBackupsDefault.cs | 22 + ...12_AddDockerRegistryCredential.Designer.cs | 2697 ++++++++++ ...60527143412_AddDockerRegistryCredential.cs | 80 + ...27151548_AddAppSecretClusterId.Designer.cs | 2709 +++++++++++ .../20260527151548_AddAppSecretClusterId.cs | 50 + ...3_AddAuditEventsAndRouteHealth.Designer.cs | 2771 +++++++++++ ...0528095223_AddAuditEventsAndRouteHealth.cs | 86 + ...0260601071037_AddHarborProject.Designer.cs | 2832 +++++++++++ .../20260601071037_AddHarborProject.cs | 72 + .../20260601124715_AddRabbitMQ.Designer.cs | 3002 ++++++++++++ .../Postgres/20260601124715_AddRabbitMQ.cs | 127 + ...AddRabbitMQCredentialVaultSync.Designer.cs | 3014 ++++++++++++ ...01132337_AddRabbitMQCredentialVaultSync.cs | 50 + ...0417_AddMongoClusterIsExternal.Designer.cs | 3017 ++++++++++++ ...0260601160417_AddMongoClusterIsExternal.cs | 29 + ...63353_AddMonitoringAndAlerting.Designer.cs | 3309 +++++++++++++ ...20260601163353_AddMonitoringAndAlerting.cs | 205 + ...eWindowsSlaTargetsRouteHistory.Designer.cs | 3473 +++++++++++++ ...aintenanceWindowsSlaTargetsRouteHistory.cs | 152 + ...0601190005_AddMessagingBinding.Designer.cs | 3543 ++++++++++++++ .../20260601190005_AddMessagingBinding.cs | 65 + ...000_AddMongoClusterExternalUri.Designer.cs | 3546 ++++++++++++++ ...260601200000_AddMongoClusterExternalUri.cs | 28 + .../20260602091234_AddVpnTunnels.Designer.cs | 3773 ++++++++++++++ .../Postgres/20260602091234_AddVpnTunnels.cs | 176 + ...3450_AddVpnLifetimeAndIdentity.Designer.cs | 3787 +++++++++++++++ ...0260602103450_AddVpnLifetimeAndIdentity.cs | 62 + ...20260602124608_AddRedisCluster.Designer.cs | 3879 +++++++++++++++ .../20260602124608_AddRedisCluster.cs | 99 + ...20260602185544_AddCacheBinding.Designer.cs | 3938 +++++++++++++++ .../20260602185544_AddCacheBinding.cs | 62 + ...20260602200000_AddMongoClusterResources.cs | 51 + ...20260603080000_AddCnpgClusterIsExternal.cs | 33 + .../20260603215323_AddGitSync.Designer.cs | 4135 ++++++++++++++++ .../Postgres/20260603215323_AddGitSync.cs | 251 + ...0260604060617_AddAppGovernance.Designer.cs | 4326 ++++++++++++++++ .../20260604060617_AddAppGovernance.cs | 153 + ...stgresApplicationDbContextModelSnapshot.cs | 2149 +++++++- ...0521201903_AddExternalKeycloak.Designer.cs | 2226 +++++++++ .../20260521201903_AddExternalKeycloak.cs | 48 + ...0260522065956_AddKeycloakTheme.Designer.cs | 2290 +++++++++ .../20260522065956_AddKeycloakTheme.cs | 82 + ...22091254_AddRegisteredPostgres.Designer.cs | 2456 ++++++++++ .../20260522091254_AddRegisteredPostgres.cs | 190 + ...0456_AddRegisteredPostgresDump.Designer.cs | 2507 ++++++++++ ...0260522110456_AddRegisteredPostgresDump.cs | 60 + ...4112_AddCnpgClusterVaultSecret.Designer.cs | 2519 ++++++++++ ...0260524194112_AddCnpgClusterVaultSecret.cs | 50 + ...cloakBackupStorageLinkNullable.Designer.cs | 2518 ++++++++++ ...03017_KeycloakBackupStorageLinkNullable.cs | 61 + ...50105_AddHarborComponentConfig.Designer.cs | 2594 ++++++++++ ...20260526050105_AddHarborComponentConfig.cs | 84 + ...260526113541_AddCnpgMaxBackups.Designer.cs | 2598 ++++++++++ .../20260526113541_AddCnpgMaxBackups.cs | 29 + ...14844_FixCnpgMaxBackupsDefault.Designer.cs | 2597 ++++++++++ ...20260526114844_FixCnpgMaxBackupsDefault.cs | 22 + ...60526121554_AddMongoMaxBackups.Designer.cs | 2601 ++++++++++ .../20260526121554_AddMongoMaxBackups.cs | 29 + ...1840_FixMongoMaxBackupsDefault.Designer.cs | 2604 ++++++++++ ...0260526121840_FixMongoMaxBackupsDefault.cs | 22 + ...18_AddDockerRegistryCredential.Designer.cs | 2699 ++++++++++ ...60527143418_AddDockerRegistryCredential.cs | 80 + ...27151555_AddAppSecretClusterId.Designer.cs | 2711 +++++++++++ .../20260527151555_AddAppSecretClusterId.cs | 50 + ...9_AddAuditEventsAndRouteHealth.Designer.cs | 2773 +++++++++++ ...0528095229_AddAuditEventsAndRouteHealth.cs | 86 + ...0260601071043_AddHarborProject.Designer.cs | 2834 +++++++++++ .../20260601071043_AddHarborProject.cs | 72 + .../20260601124725_AddRabbitMQ.Designer.cs | 3004 ++++++++++++ .../SqlServer/20260601124725_AddRabbitMQ.cs | 127 + ...AddRabbitMQCredentialVaultSync.Designer.cs | 3016 ++++++++++++ ...01132344_AddRabbitMQCredentialVaultSync.cs | 50 + ...0424_AddMongoClusterIsExternal.Designer.cs | 3019 ++++++++++++ ...0260601160424_AddMongoClusterIsExternal.cs | 29 + ...63401_AddMonitoringAndAlerting.Designer.cs | 3311 +++++++++++++ ...20260601163401_AddMonitoringAndAlerting.cs | 205 + ...eWindowsSlaTargetsRouteHistory.Designer.cs | 3475 +++++++++++++ ...aintenanceWindowsSlaTargetsRouteHistory.cs | 152 + ...0601190013_AddMessagingBinding.Designer.cs | 3545 ++++++++++++++ .../20260601190013_AddMessagingBinding.cs | 65 + ...000_AddMongoClusterExternalUri.Designer.cs | 3548 ++++++++++++++ ...260601200000_AddMongoClusterExternalUri.cs | 28 + .../20260602091242_AddVpnTunnels.Designer.cs | 3775 ++++++++++++++ .../SqlServer/20260602091242_AddVpnTunnels.cs | 176 + ...3459_AddVpnLifetimeAndIdentity.Designer.cs | 3789 +++++++++++++++ ...0260602103459_AddVpnLifetimeAndIdentity.cs | 62 + ...20260602124617_AddRedisCluster.Designer.cs | 3881 +++++++++++++++ .../20260602124617_AddRedisCluster.cs | 99 + ...20260602185552_AddCacheBinding.Designer.cs | 3940 +++++++++++++++ .../20260602185552_AddCacheBinding.cs | 62 + ...20260602200000_AddMongoClusterResources.cs | 51 + ...20260603080000_AddCnpgClusterIsExternal.cs | 33 + .../20260603215335_AddGitSync.Designer.cs | 4137 ++++++++++++++++ .../SqlServer/20260603215335_AddGitSync.cs | 251 + ...0260604060626_AddAppGovernance.Designer.cs | 4328 +++++++++++++++++ .../20260604060626_AddAppGovernance.cs | 153 + ...ServerApplicationDbContextModelSnapshot.cs | 2149 +++++++- ...0521201841_AddExternalKeycloak.Designer.cs | 2215 +++++++++ .../20260521201841_AddExternalKeycloak.cs | 48 + ...0260522070001_AddKeycloakTheme.Designer.cs | 2279 +++++++++ .../Sqlite/20260522070001_AddKeycloakTheme.cs | 82 + ...22091240_AddRegisteredPostgres.Designer.cs | 2445 ++++++++++ .../20260522091240_AddRegisteredPostgres.cs | 190 + ...0446_AddRegisteredPostgresDump.Designer.cs | 2496 ++++++++++ ...0260522110446_AddRegisteredPostgresDump.cs | 60 + ...4129_AddCnpgClusterVaultSecret.Designer.cs | 2508 ++++++++++ ...0260524194129_AddCnpgClusterVaultSecret.cs | 50 + ...cloakBackupStorageLinkNullable.Designer.cs | 2507 ++++++++++ ...02958_KeycloakBackupStorageLinkNullable.cs | 61 + ...53758_AddHarborComponentConfig.Designer.cs | 2583 ++++++++++ ...20260526053758_AddHarborComponentConfig.cs | 84 + ...260526113526_AddCnpgMaxBackups.Designer.cs | 2587 ++++++++++ .../20260526113526_AddCnpgMaxBackups.cs | 29 + ...14833_FixCnpgMaxBackupsDefault.Designer.cs | 2586 ++++++++++ ...20260526114833_FixCnpgMaxBackupsDefault.cs | 25 + ...60526121543_AddMongoMaxBackups.Designer.cs | 2590 ++++++++++ .../20260526121543_AddMongoMaxBackups.cs | 29 + ...1829_FixMongoMaxBackupsDefault.Designer.cs | 2593 ++++++++++ ...0260526121829_FixMongoMaxBackupsDefault.cs | 22 + ...02_AddDockerRegistryCredential.Designer.cs | 2688 ++++++++++ ...60527143402_AddDockerRegistryCredential.cs | 80 + ...27151539_AddAppSecretClusterId.Designer.cs | 2700 ++++++++++ .../20260527151539_AddAppSecretClusterId.cs | 50 + ...3_AddAuditEventsAndRouteHealth.Designer.cs | 2762 +++++++++++ ...0528095213_AddAuditEventsAndRouteHealth.cs | 86 + ...0260601071027_AddHarborProject.Designer.cs | 2823 +++++++++++ .../Sqlite/20260601071027_AddHarborProject.cs | 72 + .../20260601124736_AddRabbitMQ.Designer.cs | 2993 ++++++++++++ .../Sqlite/20260601124736_AddRabbitMQ.cs | 127 + ...AddRabbitMQCredentialVaultSync.Designer.cs | 3005 ++++++++++++ ...01132351_AddRabbitMQCredentialVaultSync.cs | 50 + ...0410_AddMongoClusterIsExternal.Designer.cs | 3008 ++++++++++++ ...0260601160410_AddMongoClusterIsExternal.cs | 29 + ...63342_AddMonitoringAndAlerting.Designer.cs | 3300 +++++++++++++ ...20260601163342_AddMonitoringAndAlerting.cs | 205 + ...eWindowsSlaTargetsRouteHistory.Designer.cs | 3464 +++++++++++++ ...aintenanceWindowsSlaTargetsRouteHistory.cs | 152 + ...0601190020_AddMessagingBinding.Designer.cs | 3534 ++++++++++++++ .../20260601190020_AddMessagingBinding.cs | 65 + ...000_AddMongoClusterExternalUri.Designer.cs | 3537 ++++++++++++++ ...260601200000_AddMongoClusterExternalUri.cs | 28 + .../20260602091223_AddVpnTunnels.Designer.cs | 3764 ++++++++++++++ .../Sqlite/20260602091223_AddVpnTunnels.cs | 176 + ...3442_AddVpnLifetimeAndIdentity.Designer.cs | 3778 ++++++++++++++ ...0260602103442_AddVpnLifetimeAndIdentity.cs | 62 + ...20260602124559_AddRedisCluster.Designer.cs | 3870 +++++++++++++++ .../Sqlite/20260602124559_AddRedisCluster.cs | 99 + ...20260602185535_AddCacheBinding.Designer.cs | 3929 +++++++++++++++ .../Sqlite/20260602185535_AddCacheBinding.cs | 62 + ...20260602200000_AddMongoClusterResources.cs | 51 + ...20260603080000_AddCnpgClusterIsExternal.cs | 33 + .../20260603215310_AddGitSync.Designer.cs | 4126 ++++++++++++++++ .../Sqlite/20260603215310_AddGitSync.cs | 251 + ...0260604060604_AddAppGovernance.Designer.cs | 4317 ++++++++++++++++ .../Sqlite/20260604060604_AddAppGovernance.cs | 153 + .../ApplicationDbContextModelSnapshot.cs | 2149 +++++++- src/EntKube.Web/Data/MongoCluster.cs | 41 + src/EntKube.Web/Data/NotificationChannel.cs | 19 + src/EntKube.Web/Data/NotificationDelivery.cs | 15 + src/EntKube.Web/Data/RabbitMQBackup.cs | 44 + src/EntKube.Web/Data/RabbitMQCluster.cs | 72 + src/EntKube.Web/Data/RedisCluster.cs | 60 + src/EntKube.Web/Data/RedisClusterDetail.cs | 21 + .../Data/RegisteredPostgresDatabase.cs | 42 + .../Data/RegisteredPostgresDump.cs | 24 + .../Data/RegisteredPostgresInstance.cs | 66 + src/EntKube.Web/Data/SlaTarget.cs | 16 + src/EntKube.Web/Data/Tenant.cs | 2 + src/EntKube.Web/Data/VaultSecret.cs | 45 + src/EntKube.Web/Data/VpnLocalEndpoint.cs | 49 + src/EntKube.Web/Data/VpnRemoteEndpoint.cs | 54 + src/EntKube.Web/Data/VpnTunnel.cs | 97 + src/EntKube.Web/EntKube.Web.csproj | 2 + src/EntKube.Web/Program.cs | 104 +- src/EntKube.Web/Services/AlertSyncService.cs | 214 + .../Services/AppGovernanceService.cs | 444 ++ src/EntKube.Web/Services/AppOfAppsService.cs | 329 ++ src/EntKube.Web/Services/AuditService.cs | 53 + src/EntKube.Web/Services/BackupService.cs | 373 ++ src/EntKube.Web/Services/CnpgService.cs | 502 +- src/EntKube.Web/Services/ComponentCatalog.cs | 792 +++ .../Services/ComponentLifecycleService.cs | 253 +- src/EntKube.Web/Services/DatabaseService.cs | 40 +- src/EntKube.Web/Services/DeploymentService.cs | 64 +- .../Services/DeploymentSyncService.cs | 94 + .../Services/DockerRegistryService.cs | 342 ++ .../Services/ExternalRouteHealthService.cs | 109 + .../Services/ExternalRouteService.cs | 101 +- .../Services/GitOperationsService.cs | 327 ++ .../Services/GitRepositoryService.cs | 178 + src/EntKube.Web/Services/GitSyncService.cs | 280 ++ src/EntKube.Web/Services/GitWebhookService.cs | 169 + src/EntKube.Web/Services/HarborService.cs | 759 +++ .../Services/IKubernetesClientFactory.cs | 54 + src/EntKube.Web/Services/IncidentService.cs | 357 ++ src/EntKube.Web/Services/KeycloakService.cs | 546 ++- .../Services/KubernetesClientFactory.cs | 264 +- .../Services/KubernetesOperationsService.cs | 912 +++- src/EntKube.Web/Services/KubernetesS3Proxy.cs | 268 + src/EntKube.Web/Services/LokiService.cs | 397 ++ src/EntKube.Web/Services/MongoService.cs | 1191 ++++- .../Services/NotificationService.cs | 248 + .../Services/OpenStackS3Service.cs | 81 + src/EntKube.Web/Services/PrometheusService.cs | 1187 +++-- src/EntKube.Web/Services/RabbitMQService.cs | 1348 +++++ src/EntKube.Web/Services/RedisService.cs | 582 +++ .../Services/RegisteredPostgresService.cs | 941 ++++ .../Services/StorageBrowserService.cs | 396 ++ src/EntKube.Web/Services/StorageService.cs | 292 +- src/EntKube.Web/Services/TenantService.cs | 12 + .../Services/UptimeTrackingService.cs | 73 + src/EntKube.Web/Services/UserAccessService.cs | 52 + src/EntKube.Web/Services/VaultService.cs | 829 +++- src/EntKube.Web/Services/VpnService.cs | 616 +++ src/EntKube.Web/Services/YamlFormMerger.cs | 37 +- src/EntKube.Web/appsettings.Development.json | 3 + src/EntKube.Web/wwwroot/app.css | 10 + 320 files changed, 307404 insertions(+), 1187 deletions(-) create mode 100644 Dockerfile create mode 100644 src/EntKube.Web/Authorization/HasTenantAccessRequirement.cs create mode 100644 src/EntKube.Web/Components/Pages/Admin/AdminBackup.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/AppGovernancePanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/CustomerIncidentHistory.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/CustomerMaintenanceNotice.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/CustomerSlaReport.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/CustomerStatusOverview.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/PortalDeploymentDetail.razor.js create mode 100644 src/EntKube.Web/Components/Pages/Portal/PortalIdentityDetail.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/PortalRegistryDetail.razor create mode 100644 src/EntKube.Web/Components/Pages/Portal/PortalSecretsDetail.razor create mode 100644 src/EntKube.Web/Components/Pages/Shared/DockerRegistriesPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Shared/ResourceTreePanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/AlertRulesPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/AppDetail.razor.js create mode 100644 src/EntKube.Web/Components/Pages/Tenants/AppResourcesPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/CacheTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/DatabaseMonitoringPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/GitReposTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/GovernanceTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/IncidentManagement.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/LogBrowser.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/MaintenanceWindows.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/MessagingTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/NetworkingTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/NotificationChannels.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/RabbitMQMonitoringPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/RegistryTab.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/ScrapeTargetsPanel.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/SlaTargetManager.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor create mode 100644 src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor.js create mode 100644 src/EntKube.Web/Components/Pages/Tenants/TenantMonitoringOverview.razor create mode 100644 src/EntKube.Web/Data/AlertIncident.cs create mode 100644 src/EntKube.Web/Data/AppNetworkPolicy.cs create mode 100644 src/EntKube.Web/Data/AppQuota.cs create mode 100644 src/EntKube.Web/Data/AppRbacPolicy.cs create mode 100644 src/EntKube.Web/Data/AuditEvent.cs create mode 100644 src/EntKube.Web/Data/CacheBinding.cs create mode 100644 src/EntKube.Web/Data/DeploymentHealthSnapshot.cs create mode 100644 src/EntKube.Web/Data/DockerRegistryCredential.cs create mode 100644 src/EntKube.Web/Data/DockerRegistryType.cs create mode 100644 src/EntKube.Web/Data/ExternalRouteHealthHistory.cs create mode 100644 src/EntKube.Web/Data/GitAuthType.cs create mode 100644 src/EntKube.Web/Data/GitKnownHost.cs create mode 100644 src/EntKube.Web/Data/GitRepository.cs create mode 100644 src/EntKube.Web/Data/HarborComponentConfig.cs create mode 100644 src/EntKube.Web/Data/HarborProject.cs create mode 100644 src/EntKube.Web/Data/IncidentNote.cs create mode 100644 src/EntKube.Web/Data/KeycloakTheme.cs create mode 100644 src/EntKube.Web/Data/MaintenanceWindow.cs create mode 100644 src/EntKube.Web/Data/MessagingBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260602200000_AddMongoClusterResources.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260603080000_AddCnpgClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260602200000_AddMongoClusterResources.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260603080000_AddCnpgClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260602200000_AddMongoClusterResources.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260603080000_AddCnpgClusterIsExternal.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.Designer.cs create mode 100644 src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.cs create mode 100644 src/EntKube.Web/Data/NotificationChannel.cs create mode 100644 src/EntKube.Web/Data/NotificationDelivery.cs create mode 100644 src/EntKube.Web/Data/RabbitMQBackup.cs create mode 100644 src/EntKube.Web/Data/RabbitMQCluster.cs create mode 100644 src/EntKube.Web/Data/RedisCluster.cs create mode 100644 src/EntKube.Web/Data/RedisClusterDetail.cs create mode 100644 src/EntKube.Web/Data/RegisteredPostgresDatabase.cs create mode 100644 src/EntKube.Web/Data/RegisteredPostgresDump.cs create mode 100644 src/EntKube.Web/Data/RegisteredPostgresInstance.cs create mode 100644 src/EntKube.Web/Data/SlaTarget.cs create mode 100644 src/EntKube.Web/Data/VpnLocalEndpoint.cs create mode 100644 src/EntKube.Web/Data/VpnRemoteEndpoint.cs create mode 100644 src/EntKube.Web/Data/VpnTunnel.cs create mode 100644 src/EntKube.Web/Services/AlertSyncService.cs create mode 100644 src/EntKube.Web/Services/AppGovernanceService.cs create mode 100644 src/EntKube.Web/Services/AppOfAppsService.cs create mode 100644 src/EntKube.Web/Services/AuditService.cs create mode 100644 src/EntKube.Web/Services/BackupService.cs create mode 100644 src/EntKube.Web/Services/DeploymentSyncService.cs create mode 100644 src/EntKube.Web/Services/DockerRegistryService.cs create mode 100644 src/EntKube.Web/Services/ExternalRouteHealthService.cs create mode 100644 src/EntKube.Web/Services/GitOperationsService.cs create mode 100644 src/EntKube.Web/Services/GitRepositoryService.cs create mode 100644 src/EntKube.Web/Services/GitSyncService.cs create mode 100644 src/EntKube.Web/Services/GitWebhookService.cs create mode 100644 src/EntKube.Web/Services/HarborService.cs create mode 100644 src/EntKube.Web/Services/IncidentService.cs create mode 100644 src/EntKube.Web/Services/KubernetesS3Proxy.cs create mode 100644 src/EntKube.Web/Services/LokiService.cs create mode 100644 src/EntKube.Web/Services/NotificationService.cs create mode 100644 src/EntKube.Web/Services/RabbitMQService.cs create mode 100644 src/EntKube.Web/Services/RedisService.cs create mode 100644 src/EntKube.Web/Services/RegisteredPostgresService.cs create mode 100644 src/EntKube.Web/Services/StorageBrowserService.cs create mode 100644 src/EntKube.Web/Services/UptimeTrackingService.cs create mode 100644 src/EntKube.Web/Services/UserAccessService.cs create mode 100644 src/EntKube.Web/Services/VpnService.cs diff --git a/.dockerignore b/.dockerignore index f54105e..d3efc04 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,3 +14,5 @@ **/secrets.dev.yaml **/values.dev.yaml **/Tiltfile +tests +CMKS - Infra diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b367782 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +COPY Directory.Build.props ./ +COPY src/EntKube.Web/EntKube.Web.csproj src/EntKube.Web/ +COPY src/EntKube.Web.Client/EntKube.Web.Client.csproj src/EntKube.Web.Client/ +RUN dotnet restore src/EntKube.Web/EntKube.Web.csproj + +COPY src/ src/ +RUN dotnet publish src/EntKube.Web/EntKube.Web.csproj \ + --no-restore \ + -c Release \ + -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app + +# libgit2sharp bundles its native lib, but needs libssl/libcurl on Debian +RUN apt-get update && apt-get install -y --no-install-recommends \ + libssl3 \ + libcurl4 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /app/publish . + +ENV ASPNETCORE_URLS=http://+:8080 +EXPOSE 8080 + +ENTRYPOINT ["dotnet", "EntKube.Web.dll"] diff --git a/src/EntKube.Web/Authorization/HasTenantAccessRequirement.cs b/src/EntKube.Web/Authorization/HasTenantAccessRequirement.cs new file mode 100644 index 0000000..df73986 --- /dev/null +++ b/src/EntKube.Web/Authorization/HasTenantAccessRequirement.cs @@ -0,0 +1,32 @@ +using System.Security.Claims; +using EntKube.Web.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Authorization; + +public class HasTenantAccessRequirement : IAuthorizationRequirement { } + +public class HasTenantAccessHandler(IDbContextFactory dbFactory) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, + HasTenantAccessRequirement requirement) + { + if (context.User.Identity?.IsAuthenticated != true) return; + + if (context.User.IsInRole("Admin")) + { + context.Succeed(requirement); + return; + } + + string? userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (userId is null) return; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + if (await db.TenantMemberships.AnyAsync(m => m.UserId == userId)) + context.Succeed(requirement); + } +} diff --git a/src/EntKube.Web/Components/Layout/NavMenu.razor b/src/EntKube.Web/Components/Layout/NavMenu.razor index b9fb14e..3bea427 100644 --- a/src/EntKube.Web/Components/Layout/NavMenu.razor +++ b/src/EntKube.Web/Components/Layout/NavMenu.razor @@ -1,6 +1,8 @@ -@implements IDisposable +@implements IDisposable @inject NavigationManager NavigationManager +@inject AuthenticationStateProvider AuthStateProvider +@inject EntKube.Web.Services.UserAccessService UserAccessService

Override the chart's default values (YAML format).

- - +
+ + + @if (helmValuesSaved) + { + Saved + } +
+ + @if (helmInstallOutput is not null) + { +
+
+ + + @(helmInstallSuccess == true ? "Release applied successfully" : "Helm operation failed") + + +
+
@helmInstallOutput
+
+ }
} - @* ── Resources sub-tab (ArgoCD-style tree) ── *@ + @* ── Resources sub-tab (ArgoCD-style ownership tree) ── *@ @if (deploymentSection == "resources") {
@@ -379,28 +587,12 @@ Resource Tree
- @if (resourceTree is null) - { -
-
-
- } - else if (resourceTree.Count == 0) - { -
- -

No tracked resources yet. Resources appear here after the deployment syncs to the cluster.

-
- } - else - { - @foreach (DeploymentResource root in resourceTree) - { -
- @RenderResource(root, 0) -
- } - } +
} @@ -521,6 +713,220 @@ } + + @* ── Cache bindings sub-tab ── *@ + @if (deploymentSection == "cache") + { +
+
+
+ + Cache Bindings +
+ +
+
+ @if (!string.IsNullOrEmpty(cacheBindingError)) + { +
@cacheBindingError
+ } + + @if (showAddCacheBinding) + { +
+
+
Attach a Redis Cluster
+
+
+ + +
+
+ + +
The K8s Secret created in @selectedDeployment?.Namespace.
+
+
+
+ + +
+
+
+ } + + @if (cacheBindings is null) + { +
+
+
+ } + else if (cacheBindings.Count == 0) + { +
+ +

No Redis clusters attached. Attach one to have REDIS_HOST, REDIS_PORT, and REDIS_PASSWORD synced into this deployment's namespace.

+
+ } + else + { + + + + + + + + + + + @foreach (CacheBinding binding in cacheBindings) + { + + + + + + + } + +
Redis ClusterSecret nameLast synced
+ + @binding.RedisCluster.Name + @binding.RedisCluster.KubernetesCluster.Name + @binding.KubernetesSecretName + @(binding.LastSyncedAt.HasValue ? binding.LastSyncedAt.Value.ToString("yyyy-MM-dd HH:mm") : "Never") + + +
+ } +
+
+ } + + @* ── Metrics sub-tab ── *@ + @if (deploymentSection == "metrics") + { +
+
+ Namespace Metrics + @selectedDeployment.Namespace +
+
+ @if (metricsLoading) + { +
+ } + else if (metricsError is not null) + { +
+ @metricsError +
+ } + else if (deploymentMetrics is not null) + { +
+
+
+
@(deploymentMetrics.CpuCores >= 1 ? $"{(int)Math.Round(deploymentMetrics.CpuCores)} cores" : $"{(int)Math.Round(deploymentMetrics.CpuCores * 1000)}m")
+
CPU cores (5 m avg)
+
+
+
+
+
@deploymentMetrics.MemoryMiB.ToString("F0") MiB
+
Memory (working set)
+
+
+
+
+
@deploymentMetrics.PodCount
+
Pods in namespace
+
+
+
+
+
@deploymentMetrics.RestartCount
+
Container restarts (total)
+
+
+
+
+ Queried @deploymentMetrics.QueriedAt.ToLocalTime().ToString("HH:mm:ss") +
+ } +
+
+ } + + @* ── Activity sub-tab ── *@ + @if (deploymentSection == "activity") + { +
+
+ Activity Log +
+
+ @if (activityLoading) + { +
+ } + else if (deploymentAuditEvents is not null && deploymentAuditEvents.Count == 0) + { +
No activity recorded yet.
+ } + else if (deploymentAuditEvents is not null) + { +
+ @foreach (AuditEvent ev in deploymentAuditEvents) + { +
+
+
+ @ev.ResourceKind + @ev.Action + @if (!string.IsNullOrEmpty(ev.ResourceName)) + { + @ev.ResourceName + } + @if (!string.IsNullOrEmpty(ev.Details)) + { + — @ev.Details + } +
+
+
@ev.OccurredAt.ToLocalTime().ToString("MMM d HH:mm")
+ @if (!string.IsNullOrEmpty(ev.PerformedBy)) + { +
@ev.PerformedBy
+ } +
+
+
+ } +
+ } +
+
+ } } else { @@ -549,10 +955,13 @@
- + + +
@@ -611,6 +1020,50 @@ } + @if (newDeployType is DeploymentType.GitYaml or DeploymentType.GitHelm or DeploymentType.GitAppOfApps) + { +
+
+ + + @if (gitRepos is not null && gitRepos.Count == 0) + { +
+ No repositories registered. Add one in the Git tab first. +
+ } +
+
+ + +
Path inside the repo (use . for root).
+
+
+ + +
+
+
+
+ + +
+
+ } + @if (!string.IsNullOrEmpty(createDeployError)) {
@createDeployError
@@ -681,6 +1134,87 @@ } } +@* ═══════════════════════════════════════════════════════════════════ + App Resources Tab — ArgoCD-style grid of all deployments with live trees. + ═══════════════════════════════════════════════════════════════════ *@ +@if (section == "app-resources") +{ + +} + +@* ═══════════════════════════════════════════════════════════════════ + Governance Tab — namespace, quotas, network policies, RBAC. + ═══════════════════════════════════════════════════════════════════ *@ +@if (section == "governance") +{ + +} + +@* ═══════════════════════════════════════════════════════════════════ + App Metrics Tab — aggregate CPU, memory, and pods across all deployments. + ═══════════════════════════════════════════════════════════════════ *@ +@if (section == "app-metrics") +{ +
+
+
+ + App Metrics + aggregate across all deployments +
+ +
+
+ @if (appMetricsLoading) + { +
+ } + else if (appMetricsError is not null) + { +
+ @appMetricsError +
+ } + else if (appMetrics is not null) + { +
+
+
+
+ @(appMetrics.CpuCores >= 1 ? $"{(int)Math.Round(appMetrics.CpuCores)} cores" : $"{(int)Math.Round(appMetrics.CpuCores * 1000)}m") +
+
CPU (5 m avg)
+
+
+
+
+
@appMetrics.MemoryMiB.ToString("F0") MiB
+
Memory (working set)
+
+
+
+
+
@appMetrics.PodCount
+
Pods running
+
+
+
+
+
@appMetrics.RestartCount
+
Container restarts
+
+
+
+
+ Queried @appMetrics.QueriedAt.ToLocalTime().ToString("HH:mm:ss") +
+ } +
+
+} + @code { [Parameter] public required Data.App App { get; set; } [Parameter] public Guid TenantId { get; set; } @@ -714,9 +1248,31 @@ private string newHelmChartVersion = ""; private string? createDeployError; + // Git fields for create form + private List? gitRepos; + private Guid newGitRepoId; + private string newGitPath = "."; + private string newGitRevision = ""; + private bool newGitAutoSync = true; + + // Git sync panel on deployment detail + private bool syncingGit; + private string? gitSyncResult; + private bool gitSyncSuccess; + + // Edit deployment form + private bool showEditDeployment; + private string editDeployName = ""; + private string editDeployNamespace = ""; + private string editHelmRepoUrl = ""; + private string editHelmChartName = ""; + private string editHelmChartVersion = ""; + private string? editDeployError; + // Manifest management private List? manifests; private bool showAddManifest; + private bool savingManifest; private string newManifestKind = ""; private string newManifestName = ""; private string newManifestYaml = ""; @@ -726,9 +1282,40 @@ // Helm values private string helmValuesYaml = ""; + private bool savingHelmValues; + private bool helmValuesSaved; + private string? helmValuesError; + + // Helm install / upgrade + private bool installingHelm; + private bool? helmInstallSuccess; + private string? helmInstallOutput; + + // YAML apply + private bool applyingYaml; + private bool? yamlApplySuccess; + private string? yamlApplyOutput; + + // JS module for reading live textarea values + private IJSObjectReference? jsModule; // Resource tree private List? resourceTree; + private string? resourcesError; + + // Metrics + private DeploymentMetricsSummary? deploymentMetrics; + private bool metricsLoading; + private string? metricsError; + + // Activity + private List? deploymentAuditEvents; + private bool activityLoading; + + // App-level aggregate metrics + private DeploymentMetricsSummary? appMetrics; + private bool appMetricsLoading; + private string? appMetricsError; // Database bindings private List? databaseBindings; @@ -738,11 +1325,34 @@ private string newBindingSecretName = ""; private string? bindingError; + // Cache bindings + private List? cacheBindings; + private List? availableRedisClusters; + private bool showAddCacheBinding; + private Guid newCacheBindingClusterId; + private string newCacheBindingSecretName = "redis-cache"; + private string? cacheBindingError; + protected override async Task OnInitializedAsync() { await LoadEnvironments(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + jsModule = await JS.InvokeAsync( + "import", "./Components/Pages/Tenants/AppDetail.razor.js"); + } + } + + public async ValueTask DisposeAsync() + { + if (jsModule is not null) + await jsModule.DisposeAsync(); + } + // ──────── App ──────── private async Task DeleteApp() @@ -800,10 +1410,28 @@ deployments = await DeploymentService.GetDeploymentsAsync(App.Id); } + private async Task OnNewDeployTypeChanged() + { + bool isGit = newDeployType is DeploymentType.GitYaml + or DeploymentType.GitHelm or DeploymentType.GitAppOfApps; + + if (isGit && gitRepos is null) + gitRepos = await GitRepoService.GetRepositoriesAsync(TenantId); + } + private async Task CreateDeployment() { createDeployError = null; + bool isGit = newDeployType is DeploymentType.GitYaml + or DeploymentType.GitHelm or DeploymentType.GitAppOfApps; + + if (isGit && newGitRepoId == Guid.Empty) + { + createDeployError = "Select a Git repository."; + return; + } + try { await DeploymentService.CreateDeploymentAsync( @@ -811,15 +1439,16 @@ newDeployEnvId, newDeployClusterId, newDeployNamespace, newDeployType == DeploymentType.HelmChart ? newHelmRepoUrl : null, newDeployType == DeploymentType.HelmChart ? newHelmChartName : null, - newDeployType == DeploymentType.HelmChart ? newHelmChartVersion : null); + newDeployType == DeploymentType.HelmChart ? newHelmChartVersion : null, + gitRepositoryId: isGit ? newGitRepoId : null, + gitPath: isGit ? newGitPath : null, + gitRevision: isGit && !string.IsNullOrWhiteSpace(newGitRevision) ? newGitRevision : null, + gitAutoSync: isGit && newGitAutoSync); - // Reset form and reload. showCreateDeployment = false; - newDeployName = ""; - newDeployNamespace = ""; - newHelmRepoUrl = ""; - newHelmChartName = ""; - newHelmChartVersion = ""; + newDeployName = ""; newDeployNamespace = ""; + newHelmRepoUrl = ""; newHelmChartName = ""; newHelmChartVersion = ""; + newGitRepoId = Guid.Empty; newGitPath = "."; newGitRevision = ""; newGitAutoSync = true; await LoadDeployments(); } catch (DbUpdateException) @@ -828,17 +1457,66 @@ } } + private async Task TriggerGitSync() + { + if (selectedDeployment is null) return; + syncingGit = true; + gitSyncResult = null; + StateHasChanged(); + try + { + GitSyncResult result = await GitSyncService.SyncDeploymentAsync(selectedDeployment.Id); + gitSyncSuccess = result.IsSuccess; + gitSyncResult = result.AlreadyCurrent + ? "Already up to date." + : result.IsSuccess + ? $"Synced to {result.CommitSha[..Math.Min(7, result.CommitSha.Length)]} — {result.CommitMessage}" + : result.Error ?? "Sync failed."; + + if (result.IsSuccess) + { + // Reload to get updated LastSyncedAt / commit + await LoadDeployments(); + selectedDeployment = deployments?.FirstOrDefault(d => d.Id == selectedDeployment.Id) + ?? selectedDeployment; + } + } + catch (Exception ex) { gitSyncSuccess = false; gitSyncResult = ex.Message; } + finally { syncingGit = false; } + } + private async Task SelectDeployment(AppDeployment deploy) { selectedDeployment = deploy; confirmDeleteDeployment = false; + helmValuesSaved = false; + helmValuesError = null; + helmInstallOutput = null; + helmInstallSuccess = null; + yamlApplyOutput = null; + yamlApplySuccess = null; + savingManifest = false; + editingManifestId = null; + resourceTree = null; + resourcesError = null; + deploymentMetrics = null; + metricsError = null; + deploymentAuditEvents = null; + + gitSyncResult = null; + syncingGit = false; // Default to the appropriate sub-tab based on type. - if (deploy.Type == DeploymentType.HelmChart) + if (deploy.Type == DeploymentType.HelmChart || deploy.Type == DeploymentType.GitHelm) { deploymentSection = "helm-values"; helmValuesYaml = deploy.HelmValues ?? ""; } + else if (deploy.Type == DeploymentType.GitAppOfApps) + { + deploymentSection = "resources"; + await OpenResourcesTab(); + } else { deploymentSection = "manifests"; @@ -857,6 +1535,45 @@ } } + private void StartEditDeployment() + { + if (selectedDeployment is null) return; + editDeployName = selectedDeployment.Name; + editDeployNamespace = selectedDeployment.Namespace; + editHelmRepoUrl = selectedDeployment.HelmRepoUrl ?? ""; + editHelmChartName = selectedDeployment.HelmChartName ?? ""; + editHelmChartVersion = selectedDeployment.HelmChartVersion ?? ""; + editDeployError = null; + showEditDeployment = true; + confirmDeleteDeployment = false; + } + + private async Task SaveDeployment() + { + if (selectedDeployment is null) return; + + editDeployError = null; + + try + { + await DeploymentService.UpdateDeploymentAsync( + selectedDeployment.Id, + editDeployName, + editDeployNamespace, + selectedDeployment.Type == DeploymentType.HelmChart ? editHelmRepoUrl : null, + selectedDeployment.Type == DeploymentType.HelmChart ? editHelmChartName : null, + selectedDeployment.Type == DeploymentType.HelmChart ? editHelmChartVersion : null); + + await LoadDeployments(); + selectedDeployment = deployments?.FirstOrDefault(d => d.Id == selectedDeployment.Id) ?? selectedDeployment; + showEditDeployment = false; + } + catch (Exception ex) + { + editDeployError = ex.Message; + } + } + // ──────── Manifests ──────── private async Task LoadManifests() @@ -869,20 +1586,24 @@ private async Task AddManifest() { - if (selectedDeployment is null) + if (selectedDeployment is null) return; + + if (jsModule is not null) + newManifestYaml = await jsModule.InvokeAsync("getElementValue", "new-manifest-yaml"); + + savingManifest = true; + try { - return; + await DeploymentService.AddManifestAsync( + selectedDeployment.Id, newManifestKind, newManifestName, newManifestYaml, newManifestSortOrder); + showAddManifest = false; + newManifestKind = ""; + newManifestName = ""; + newManifestYaml = ""; + newManifestSortOrder = 0; + await LoadManifests(); } - - await DeploymentService.AddManifestAsync( - selectedDeployment.Id, newManifestKind, newManifestName, newManifestYaml, newManifestSortOrder); - - showAddManifest = false; - newManifestKind = ""; - newManifestName = ""; - newManifestYaml = ""; - newManifestSortOrder = 0; - await LoadManifests(); + finally { savingManifest = false; } } private void StartEditManifest(DeploymentManifest manifest) @@ -893,9 +1614,17 @@ private async Task SaveManifest(Guid manifestId) { - await DeploymentService.UpdateManifestAsync(manifestId, editManifestYaml); - editingManifestId = null; - await LoadManifests(); + if (jsModule is not null) + editManifestYaml = await jsModule.InvokeAsync("getElementValue", $"edit-manifest-yaml-{manifestId}"); + + savingManifest = true; + try + { + await DeploymentService.UpdateManifestAsync(manifestId, editManifestYaml); + editingManifestId = null; + await LoadManifests(); + } + finally { savingManifest = false; } } private async Task DeleteManifest(Guid manifestId) @@ -904,14 +1633,71 @@ await LoadManifests(); } + // ──────── YAML Apply ──────── + + private async Task ApplyYamlDeployment() + { + if (selectedDeployment is null) return; + + applyingYaml = true; + yamlApplyOutput = null; + yamlApplySuccess = null; + + try + { + KubernetesOperationResult result = + await K8sOps.ApplyYamlDeploymentAsync(selectedDeployment.Id); + + yamlApplySuccess = result.IsSuccess; + yamlApplyOutput = result.IsSuccess ? result.Data : result.Error; + } + finally { applyingYaml = false; } + } + + // ──────── Helm Install / Upgrade ──────── + + private async Task InstallOrUpgradeHelm() + { + if (selectedDeployment is null) return; + + installingHelm = true; + helmInstallOutput = null; + helmInstallSuccess = null; + + try + { + KubernetesOperationResult result = + await K8sOps.HelmInstallOrUpgradeAsync(selectedDeployment.Id); + + helmInstallSuccess = result.IsSuccess; + helmInstallOutput = result.IsSuccess ? result.Data : result.Error; + } + finally { installingHelm = false; } + } + // ──────── Helm Values ──────── private async Task SaveHelmValues() { - if (selectedDeployment is not null) + // 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("getElementValue", "helm-values-editor"); + + savingHelmValues = true; + helmValuesSaved = false; + helmValuesError = null; + try { - await DeploymentService.UpdateHelmValuesAsync(selectedDeployment.Id, helmValuesYaml); + if (selectedDeployment is not null) + { + await DeploymentService.UpdateHelmValuesAsync(selectedDeployment.Id, helmValuesYaml); + selectedDeployment.HelmValues = helmValuesYaml; + } + helmValuesSaved = true; } + catch (Exception ex) { helmValuesError = ex.Message; } + finally { savingHelmValues = false; } } // ──────── Resources ──────── @@ -919,10 +1705,42 @@ private async Task OpenResourcesTab() { deploymentSection = "resources"; + resourceTree = null; // trigger spinner + resourcesError = null; if (selectedDeployment is not null) { - resourceTree = await DeploymentService.GetResourceTreeAsync(selectedDeployment.Id); + KubernetesOperationResult> result = + await K8sOps.GetLiveResourcesAsync(selectedDeployment.Id); + + if (result.IsSuccess) + { + resourceTree = result.Data ?? []; + + // Derive live status from the resource tree and persist it so + // the header badges (and the deployment list card) show real values. + (SyncStatus sync, HealthStatus health) = + KubernetesOperationsService.ComputeStatusFromResources(resourceTree); + + await DeploymentService.UpdateDeploymentStatusAsync( + selectedDeployment.Id, sync, health); + + // Update in-memory objects so UI refreshes immediately without a reload. + selectedDeployment.SyncStatus = sync; + selectedDeployment.HealthStatus = health; + + AppDeployment? listEntry = deployments?.FirstOrDefault(d => d.Id == selectedDeployment.Id); + if (listEntry is not null) + { + listEntry.SyncStatus = sync; + listEntry.HealthStatus = health; + } + } + else + { + resourcesError = result.Error; + resourceTree = []; + } } } @@ -946,6 +1764,59 @@ } } + private async Task OpenMetricsTab() + { + deploymentSection = "metrics"; + if (selectedDeployment is null) return; + + metricsLoading = true; + metricsError = null; + deploymentMetrics = null; + StateHasChanged(); + + KubernetesOperationResult result = + await PrometheusService.GetDeploymentMetricsAsync(selectedDeployment.Id); + + if (result.IsSuccess) + deploymentMetrics = result.Data; + else + metricsError = result.Error; + + metricsLoading = false; + } + + private async Task OpenActivityTab() + { + deploymentSection = "activity"; + if (selectedDeployment is null) return; + + activityLoading = true; + deploymentAuditEvents = null; + StateHasChanged(); + + deploymentAuditEvents = await AuditService.GetDeploymentEventsAsync(selectedDeployment.Id); + activityLoading = false; + } + + private async Task OpenAppMetricsSection() + { + section = "app-metrics"; + appMetricsLoading = true; + appMetricsError = null; + appMetrics = null; + StateHasChanged(); + + KubernetesOperationResult result = + await PrometheusService.GetAppMetricsAsync(App.Id); + + if (result.IsSuccess) + appMetrics = result.Data; + else + appMetricsError = result.Error; + + appMetricsLoading = false; + } + private async Task AddDatabaseBinding() { if (selectedDeployment is null || newBindingDatabaseId == Guid.Empty) @@ -1000,6 +1871,59 @@ } } + // ──────── Cache bindings ──────── + + private async Task OpenCacheTab() + { + deploymentSection = "cache"; + + if (cacheBindings is null && selectedDeployment is not null) + { + cacheBindings = await RedisService.GetCacheBindingsForDeploymentAsync(selectedDeployment.Id); + availableRedisClusters = await RedisService.GetClustersAsync(TenantId); + } + } + + private async Task AddCacheBinding() + { + if (selectedDeployment is null || newCacheBindingClusterId == Guid.Empty) return; + + cacheBindingError = null; + + try + { + await RedisService.CreateCacheBindingAsync( + TenantId, newCacheBindingClusterId, selectedDeployment.Id, + newCacheBindingSecretName.Trim()); + + showAddCacheBinding = false; + newCacheBindingClusterId = Guid.Empty; + newCacheBindingSecretName = "redis-cache"; + cacheBindings = await RedisService.GetCacheBindingsForDeploymentAsync(selectedDeployment.Id); + } + catch (Exception ex) + { + cacheBindingError = $"Failed to attach Redis cluster: {ex.Message}"; + } + } + + private async Task RemoveCacheBinding(Guid bindingId) + { + cacheBindingError = null; + + try + { + await RedisService.DeleteCacheBindingAsync(TenantId, bindingId); + + if (selectedDeployment is not null) + cacheBindings = await RedisService.GetCacheBindingsForDeploymentAsync(selectedDeployment.Id); + } + catch (Exception ex) + { + cacheBindingError = $"Failed to detach: {ex.Message}"; + } + } + // ──────── Rendering helpers ──────── private RenderFragment GetTypeBadge(DeploymentType type) => type switch @@ -1029,22 +1953,4 @@ _ => @Unknown }; - private RenderFragment RenderResource(DeploymentResource resource, int depth) => __builder => - { -
- @resource.Kind - @resource.Name -
- @GetSyncBadge(resource.SyncStatus) - @GetHealthBadge(resource.HealthStatus) -
-
- @if (resource.ChildResources?.Count > 0) - { - @foreach (DeploymentResource child in resource.ChildResources) - { - @RenderResource(child, depth + 1) - } - } - }; } diff --git a/src/EntKube.Web/Components/Pages/Tenants/AppDetail.razor.js b/src/EntKube.Web/Components/Pages/Tenants/AppDetail.razor.js new file mode 100644 index 0000000..f2e045f --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/AppDetail.razor.js @@ -0,0 +1,4 @@ +export function getElementValue(id) { + const el = document.getElementById(id); + return el ? el.value : ''; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/AppResourcesPanel.razor b/src/EntKube.Web/Components/Pages/Tenants/AppResourcesPanel.razor new file mode 100644 index 0000000..c918c9d --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/AppResourcesPanel.razor @@ -0,0 +1,334 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject DeploymentService DeploymentService +@inject KubernetesOperationsService K8sOps + +@* ═══════════════════════════════════════════════════════════════════ + AppResourcesPanel — app-level resource overview. + + Shows each deployment as an ArgoCD-style Application card. Clicking + a card expands its resource tree inline. Multiple cards can be open + simultaneously. + + Card layout mirrors the ArgoCD Application grid: + ┌──────────────────────────────────────┐ + │ [icon] deployment-name │ + │ [type] env · cluster · ns │ + │ ● Healthy ○ Synced │ + └──────────────────────────────────────┘ + ═══════════════════════════════════════════════════════════════════ *@ + + + +
+ @if (deployments is null) + { + @for (int i = 0; i < 3; i++) + { +
+
+
+
+
Loading…
+
+
+
+
+ } + } + else if (deployments.Count == 0) + { +
+ +

No deployments yet. Create one in the Deployments tab.

+
+ } + else + { + @foreach (AppDeployment d in deployments) + { + string hc = HealthColor(d.HealthStatus); + bool open = expandedIds.Contains(d.Id); + +
+ +
+
+ +
+
+
@d.Name
+
+ @TypeLabel(d.Type) · @d.Environment?.Name · @d.Namespace +
+
+ +
+ +
+ @{ + string hcol = HealthColor(d.HealthStatus); + string hlbl = HealthLabel(d.HealthStatus); + var (scol, sico, slbl) = SyncInfo(d.SyncStatus); + } + + ● @hlbl + + + @slbl + + @if (d.GitRepositoryId is not null && d.GitLastSyncedCommit is not null) + { + + @d.GitLastSyncedCommit[..7] + + } +
+ + @if (d.Cluster is not null) + { +
+ @d.Cluster.Name +
+ } +
+ } + } +
+ +@* ── Expanded resource trees ─────────────────────────────────────────────── *@ +@if (deployments is not null) +{ + @foreach (AppDeployment d in deployments.Where(d => expandedIds.Contains(d.Id))) + { +
+
+
+ + @d.Name + / @d.Namespace + @if (loadingIds.Contains(d.Id)) + { +
+ } +
+ +
+ + @if (treeErrors.TryGetValue(d.Id, out string? err)) + { +
+ @err +
+ } + else + { + + } +
+ } +} + +@code { + [Parameter, EditorRequired] public Guid AppId { get; set; } + + private List? deployments; + private HashSet expandedIds = []; + private HashSet loadingIds = []; + private Dictionary> trees = []; + private Dictionary treeErrors = []; + + protected override async Task OnInitializedAsync() + { + deployments = await DeploymentService.GetDeploymentsAsync(AppId); + } + + private async Task ToggleDeployment(Guid id) + { + if (expandedIds.Contains(id)) + { + expandedIds.Remove(id); + return; + } + + expandedIds.Add(id); + + if (trees.ContainsKey(id)) return; + + loadingIds.Add(id); + treeErrors.Remove(id); + StateHasChanged(); + + KubernetesOperationResult> result = + await K8sOps.GetLiveResourcesAsync(id); + + loadingIds.Remove(id); + + if (result.IsSuccess && result.Data is not null) + { + trees[id] = result.Data; + (SyncStatus sync, HealthStatus health) = KubernetesOperationsService.ComputeStatusFromResources(result.Data); + AppDeployment? d = deployments?.FirstOrDefault(x => x.Id == id); + if (d is not null) { d.SyncStatus = sync; d.HealthStatus = health; } + } + else + { + treeErrors[id] = result.Error ?? "Failed to fetch resources."; + } + } + + // ── Badge / label helpers ───────────────────────────────────────────────── + + private static string HealthColor(HealthStatus h) => h switch + { + HealthStatus.Healthy => "#198754", + HealthStatus.Progressing => "#0d6efd", + HealthStatus.Degraded => "#fd7e14", + HealthStatus.Missing => "#dc3545", + HealthStatus.Suspended => "#6c757d", + _ => "#adb5bd", + }; + + private static string HealthLabel(HealthStatus h) => h switch + { + HealthStatus.Healthy => "Healthy", + HealthStatus.Progressing => "Progressing", + HealthStatus.Degraded => "Degraded", + HealthStatus.Missing => "Missing", + HealthStatus.Suspended => "Suspended", + _ => "Unknown", + }; + + private static (string color, string icon, string label) SyncInfo(SyncStatus s) => s switch + { + SyncStatus.Synced => ("#198754", "bi-check-circle", "Synced"), + SyncStatus.OutOfSync => ("#fd7e14", "bi-arrow-left-right", "OutOfSync"), + SyncStatus.Syncing => ("#0d6efd", "bi-arrow-repeat", "Syncing"), + SyncStatus.Failed => ("#dc3545", "bi-x-circle", "Failed"), + _ => ("#adb5bd", "bi-question-circle", "Unknown"), + }; + + private static string TypeIcon(DeploymentType t) => t switch + { + DeploymentType.HelmChart => "bi-box-seam", + DeploymentType.GitYaml => "bi-git", + DeploymentType.GitHelm => "bi-git", + DeploymentType.GitAppOfApps => "bi-diagram-2", + _ => "bi-rocket-takeoff", + }; + + private static string TypeLabel(DeploymentType t) => t switch + { + DeploymentType.Manual => "Manual", + DeploymentType.Yaml => "YAML", + DeploymentType.HelmChart => "Helm", + DeploymentType.GitYaml => "Git/YAML", + DeploymentType.GitHelm => "Git/Helm", + DeploymentType.GitAppOfApps => "App of Apps", + _ => t.ToString(), + }; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/CacheTab.razor b/src/EntKube.Web/Components/Pages/Tenants/CacheTab.razor new file mode 100644 index 0000000..2f09bcf --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/CacheTab.razor @@ -0,0 +1,689 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject RedisService RedisService +@inject TenantService TenantService + +@* =========================================================================== + Cache Tab — Redis cluster management via OT-Container-Kit Redis Operator. + Full lifecycle for RedisCluster CRDs (redis.redis.opstreelabs.in/v1beta2). + EntKube generates the auth password, creates the K8s Secret, and applies + the RedisCluster manifest. Credentials are surfaced from the tenant vault. + =========================================================================== *@ + +@if (loading) +{ +
+
+

Loading Redis clusters...

+
+} +else +{ + @* ── Operator availability badge ── *@ +
+ @if (operatorStatus?.OperatorAvailable == true) + { + + Redis Operator + @if (operatorStatus.OperatorClusterName is not null) + { + on @operatorStatus.OperatorClusterName + } + + } + else + { + + Redis Operator — not installed + + } +
+ + @* ── Cluster list ── *@ +
+
+ Redis Clusters + @clusters.Count + +
+ + @if (showCreateForm) + { +
+
+
New Redis Cluster
+ +
+
+
+
+ + +
DNS-safe, max 63 chars.
+
+
+ + +
+
+ + +
+
+ + +
Min 3 for cluster mode
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + @if (createError is not null) + { +
@createError
+ } + +
+ + +
+
+
+ } + + @foreach (RedisCluster cluster in clusters) + { + bool expanded = expandedId == cluster.Id; + +
+
+ + @cluster.Name + @cluster.Namespace · @cluster.KubernetesCluster.Name + @cluster.Status + @cluster.ClusterSize shards · @cluster.RedisVersion · @cluster.StorageSize/node + +
+ + @if (cluster.LastError is not null && cluster.Status == RedisClusterStatus.Failed) + { +
@cluster.LastError
+ } + + @if (expanded) + { +
+
+
Shards
+
@cluster.ClusterSize leaders + @cluster.ClusterSize followers
+
Redis Version
+
@cluster.RedisVersion
+
Persistence
+
@(cluster.PersistenceEnabled ? "Enabled (PVC)" : "Disabled")
+
Storage / Node
+
@cluster.StorageSize
+ @if (cluster.StorageClass is not null) + { +
Storage Class
+
@cluster.StorageClass
+ } +
Created
+
@cluster.CreatedAt.ToString("yyyy-MM-dd HH:mm") UTC
+
+ + @* ── Live pod status ── *@ + @if (loadingDetail && expandedId == cluster.Id) + { +
+ + Querying Kubernetes... +
+ } + else if (expandedDetail?.Cluster.Id == cluster.Id) + { +
+
+ Phase: + @expandedDetail.Phase + Leaders ready: + @expandedDetail.ReadyLeaders / @cluster.ClusterSize + Followers ready: + @expandedDetail.ReadyFollowers / @cluster.ClusterSize +
+ @if (expandedDetail.Pods.Count > 0) + { + + + + + + + + + + + + + @foreach (RedisPodInfo pod in expandedDetail.Pods) + { + + + + + + + + + } + +
PodRoleStatusReadyRestartsNode
@pod.Name + + @pod.Role + + @pod.Status + @if (pod.Ready) + { + + } + else + { + + } + @pod.Restarts@(pod.Node ?? "—")
+ } + else + { +

No pods found yet.

+ } +
+ } + + @* ── Credentials panel ── *@ + @if (credClusterId == cluster.Id) + { +
+
+ Connection Details + +
+
+ @if (loadingCreds) + { + + Loading... + } + else if (credError is not null) + { +
@credError
+ } + else + { +
+
+ Host (leader service) + @(credHost ?? "—") +
+
+ Port + @(credPort ?? "—") +
+
+ Password + @if (showCredPassword) + { + @credPassword + } + else + { + •••••••• + } + +
+
+ } +
+
+ } + + @* ── App bindings ── *@ +
+
+ App Bindings + +
+ + @if (addBindingClusterId == cluster.Id) + { +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ @if (addBindingError is not null) + { +
@addBindingError
+ } +
+
+ } + + @{ + List clusterBindings = allBindings.TryGetValue(cluster.Id, out List? b) ? b : []; + } + @if (clusterBindings.Count == 0) + { +

No app bindings yet.

+ } + else + { + + + + @foreach (CacheBinding binding in clusterBindings) + { + + + + + + + } + +
App / DeploymentSecretLast Synced
@binding.AppDeployment.App.Name / @binding.AppDeployment.Name@binding.KubernetesSecretName@(binding.LastSyncedAt.HasValue ? binding.LastSyncedAt.Value.ToString("yyyy-MM-dd HH:mm") : "Never") + +
+ } +
+ +
+ + + @if (deleteTarget?.Id == cluster.Id) + { + + Delete cluster and all its data? + + + + } + else + { + + } +
+
+ } +
+ } + + @if (clusters.Count == 0 && !showCreateForm) + { +

No Redis clusters. Install the Redis Operator from the Components tab, then create a cluster here.

+ } +
+} + +@code { + [Parameter] public Guid TenantId { get; set; } + + // ── State ── + private bool loading = true; + private List clusters = []; + private List allK8sClusters = []; + private RedisOperatorStatus? operatorStatus; + + // ── Create form ── + private bool showCreateForm; + private string createName = ""; + private Guid createK8sClusterId; + private string createNamespace = "ot-operators"; + private int createClusterSize = 3; + private string createRedisVersion = "v7.0.15"; + private string createStorageSize = "1Gi"; + private string? createStorageClass; + private bool createPersistenceEnabled = true; + private bool creating; + private string? createError; + + // ── Expand / detail ── + private Guid? expandedId; + private RedisClusterDetail? expandedDetail; + private bool loadingDetail; + + // ── Delete ── + private RedisCluster? deleteTarget; + private bool deleting; + + // ── Bindings ── + private Dictionary> allBindings = []; + private List allDeployments = []; + private Guid? addBindingClusterId; + private Guid addBindingDeploymentId; + private string addBindingSecretName = ""; + private string? addBindingError; + + // ── Credentials ── + private Guid? credClusterId; + private bool loadingCreds; + private string? credHost; + private string? credPort; + private string? credPassword; + private bool showCredPassword; + private string? credError; + + // ── Init ── + + protected override async Task OnInitializedAsync() => await LoadAsync(); + + private async Task LoadAsync() + { + loading = true; + + Task> clustersTask = RedisService.GetClustersAsync(TenantId); + Task> k8sTask = TenantService.GetClustersAsync(TenantId); + Task operatorTask = RedisService.GetOperatorStatusAsync(TenantId); + Task> deploymentsTask = RedisService.GetTenantDeploymentsAsync(TenantId); + + await Task.WhenAll(clustersTask, k8sTask, operatorTask, deploymentsTask); + + clusters = clustersTask.Result; + allK8sClusters = k8sTask.Result; + operatorStatus = operatorTask.Result; + allDeployments = deploymentsTask.Result; + + // Load bindings per cluster. + IEnumerable)>> bindingTasks = clusters.Select(async c => + (c.Id, await RedisService.GetCacheBindingsAsync(TenantId, c.Id))); + + (Guid id, List bindings)[] bindingResults = await Task.WhenAll(bindingTasks); + allBindings = bindingResults.ToDictionary(r => r.id, r => r.bindings); + + loading = false; + } + + // ── Create ── + + private void ShowCreateForm() { showCreateForm = true; createError = null; } + private void CancelCreate() { showCreateForm = false; createError = null; } + + private async Task CreateCluster() + { + if (string.IsNullOrWhiteSpace(createName) || createK8sClusterId == Guid.Empty) + { + createError = "Cluster name and Kubernetes cluster are required."; + return; + } + + creating = true; + createError = null; + + try + { + await RedisService.CreateClusterAsync( + TenantId, + createK8sClusterId, + createName.Trim(), + createNamespace.Trim(), + createClusterSize, + createRedisVersion.Trim(), + createStorageSize.Trim(), + string.IsNullOrWhiteSpace(createStorageClass) ? null : createStorageClass.Trim(), + createPersistenceEnabled); + + showCreateForm = false; + createName = ""; + createNamespace = "ot-operators"; + createClusterSize = 3; + createRedisVersion = "v7"; + createStorageSize = "1Gi"; + createStorageClass = null; + + await LoadAsync(); + } + catch (Exception ex) + { + createError = ex.Message; + } + finally + { + creating = false; + } + } + + // ── Expand / detail ── + + private async Task ToggleCluster(Guid id) + { + if (expandedId == id) + { + expandedId = null; + expandedDetail = null; + return; + } + + expandedId = id; + expandedDetail = null; + loadingDetail = true; + StateHasChanged(); + + expandedDetail = await RedisService.GetClusterDetailAsync(TenantId, id); + loadingDetail = false; + } + + // ── Delete ── + + private void ConfirmDelete(RedisCluster cluster) + { + deleteTarget = cluster; + credClusterId = null; + } + + private async Task DeleteCluster() + { + if (deleteTarget is null) return; + + deleting = true; + + try + { + await RedisService.DeleteClusterAsync(TenantId, deleteTarget.Id); + deleteTarget = null; + expandedId = null; + await LoadAsync(); + } + catch (Exception ex) + { + deleteTarget.LastError = ex.Message; + } + finally + { + deleting = false; + } + } + + // ── Bindings ── + + private void ShowAddBinding(Guid clusterId) + { + addBindingClusterId = clusterId; + addBindingDeploymentId = Guid.Empty; + addBindingSecretName = "redis-cache"; + addBindingError = null; + } + + private void CancelAddBinding() + { + addBindingClusterId = null; + addBindingError = null; + } + + private async Task AddBinding(Guid clusterId) + { + addBindingError = null; + + try + { + await RedisService.CreateCacheBindingAsync( + TenantId, clusterId, addBindingDeploymentId, addBindingSecretName.Trim()); + + addBindingClusterId = null; + addBindingSecretName = ""; + allBindings[clusterId] = await RedisService.GetCacheBindingsAsync(TenantId, clusterId); + } + catch (Exception ex) + { + addBindingError = ex.Message; + } + } + + private async Task RemoveBinding(Guid clusterId, Guid bindingId) + { + try + { + await RedisService.DeleteCacheBindingAsync(TenantId, bindingId); + allBindings[clusterId] = await RedisService.GetCacheBindingsAsync(TenantId, clusterId); + } + catch { } + } + + // ── Credentials ── + + private async Task ShowCredentials(RedisCluster cluster) + { + if (credClusterId == cluster.Id) + { + credClusterId = null; + return; + } + + credClusterId = cluster.Id; + loadingCreds = true; + credError = null; + credHost = null; + credPort = null; + credPassword = null; + showCredPassword = false; + + try + { + (string? password, string? host, string? port) = + await RedisService.GetCredentialsAsync(TenantId, cluster.Id); + + credPassword = password; + credHost = host; + credPort = port; + + if (credPassword is null) + credError = "No credentials found in vault."; + } + catch (Exception ex) + { + credError = ex.Message; + } + finally + { + loadingCreds = false; + } + } + + // ── Helpers ── + + private static string StatusBadgeClass(RedisClusterStatus status) => status switch + { + RedisClusterStatus.Running => "badge bg-success", + RedisClusterStatus.Creating => "badge bg-info text-dark", + RedisClusterStatus.Failed => "badge bg-danger", + RedisClusterStatus.Deleting => "badge bg-secondary", + _ => "badge bg-secondary" + }; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/ClusterDetail.razor b/src/EntKube.Web/Components/Pages/Tenants/ClusterDetail.razor index bf43b26..7af0781 100644 --- a/src/EntKube.Web/Components/Pages/Tenants/ClusterDetail.razor +++ b/src/EntKube.Web/Components/Pages/Tenants/ClusterDetail.razor @@ -7,6 +7,9 @@ @inject ExternalRouteService RouteService @inject ComponentScanService ScanService @inject KeycloakService KeycloakService +@inject HarborService HarborService +@inject LokiService LokiService +@inject StorageService StorageService @* ═══════════════════════════════════════════════════════════════════ Cluster Detail — a full-page view for a single cluster. @@ -75,12 +78,21 @@ Components + @if (section == "monitoring") { } +else if (section == "logs") +{ + +} else if (section == "components") { @* --- Register Component --- *@ @@ -294,7 +306,7 @@ else if (section == "components")
@foreach (ComponentFormField field in selectedCatalogEntry.FormFields) { - @if (IsFieldVisible(field, regFormFieldValues)) + @if (!field.Hidden && IsFieldVisible(field, regFormFieldValues)) {
@@ -321,6 +333,21 @@ else if (section == "components") } } + else if (field.Type == FormFieldType.StorageLink) + { + + } else if (field.Type == FormFieldType.ClusterIssuer) { @if (keycloakAvailableIssuers is { Count: > 0 }) @@ -342,6 +369,19 @@ else if (section == "components") @onchange="@(e => OnRegFieldChanged(field.Key, e))" /> } } + else if (field.Type == FormFieldType.GatewaySelector) + { + + } else if (field.Type == FormFieldType.Select && field.Options is not null) { } + else if (field.Type == FormFieldType.StorageLink) + { + + } else if (field.Type == FormFieldType.ClusterIssuer) { @if (keycloakAvailableIssuers is { Count: > 0 }) @@ -813,6 +868,19 @@ else if (section == "components") @onchange="@(e => OnEditFieldChanged(field.Key, e))" /> } } + else if (field.Type == FormFieldType.GatewaySelector) + { + + } else if (field.Type == FormFieldType.Select && field.Options is not null) { +
+
+ + +
+
+ + +
+
+ + +
Used as HOST in credentials (resolves as service.ns.svc.cluster.local)
+
+
+ + +
+
+ + +
Pod to kubectl exec into for admin SQL
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(registerPgError)) + { +
+ @registerPgError +
+ } + + + } + + @* ── Instance Cards ── *@ + @if (registeredPgInstances is null) + { +
+
+
+ } + else if (FilteredRegisteredPgInstances!.Count == 0 && !showRegisterPgForm) + { +
+ + No registered PostgreSQL instances yet. Click "Register Instance" to add a non-CNPG Postgres server. +
+ } + else + { + @foreach (RegisteredPostgresInstance rpi in FilteredRegisteredPgInstances!) + { +
+
+
+ + + @rpi.Name + @rpi.ServiceName:@rpi.Port + + @rpi.KubernetesCluster.Environment.Name + + @if (rpi.Databases.Count > 0) + { + @rpi.Databases.Count db(s) + } +
+
+ + +
+
+ + @if (testRegPgResultId == rpi.Id && testRegPgResult is not null) + { +
+
+ @testRegPgResult
} + @if (deleteRegPgError is not null && deletingRegPgId == rpi.Id) + { +
+
@deleteRegPgError
+
+ } + + @* ── Expanded Detail Panel ── *@ + @if (expandedRegPgId == rpi.Id) + { +
+ @if (!string.IsNullOrEmpty(rpi.Notes)) + { +

@rpi.Notes

+ } + + @* ── Pod Health ── *@ +
Pod
+ @if (regPgPodsLoading.GetValueOrDefault(rpi.Id)) + { +
+
+
+ } + else if (regPgPods.TryGetValue(rpi.Id, out List? pods) && pods.Count > 0) + { +
+ + + + + + + + + + + + + @foreach (CnpgPodInfo pod in pods) + { + + + + + + + + + } + +
PodStatusReadyNodeAgeRestarts
@pod.Name + @if (pod.Status == "Running") + { + @pod.Status + } + else if (pod.Status is "Pending" or "ContainerCreating") + { + @pod.Status + } + else + { + @pod.Status + } + + @if (pod.Ready) + { + + } + else + { + + } + @(pod.Node ?? "—") + @if (pod.StartTime.HasValue) + { + @((DateTime.UtcNow - pod.StartTime.Value) switch + { + var d when d.TotalDays >= 1 => $"{(int)d.TotalDays}d", + var d when d.TotalHours >= 1 => $"{(int)d.TotalHours}h", + var d => $"{(int)d.TotalMinutes}m" + }) + } + else { } + @pod.Restarts
+
+ } + else + { +

Pod info unavailable.

+ } + +
+
Databases
+ + +
+ + @* ── Create Database Form ── *@ + @if (addDbRegPgInstance?.Id == rpi.Id) + { +
+
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(addRegPgDbError)) + { +
@addRegPgDbError
+ } +
+ } + + @* ── Import Database Form ── *@ + @if (importRegPgInstance?.Id == rpi.Id) + { +
+

Register an existing database without creating it on the server. Stores the provided credentials in the vault.

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(importRegPgError)) + { +
@importRegPgError
+ } +
+ } + + @* ── Databases Table ── *@ + @if (rpi.Databases.Count == 0) + { +

No databases managed yet.

+ } + else + { +
+ + + + + + + + + + + @foreach (RegisteredPostgresDatabase db in rpi.Databases) + { + + + + + + + @if (regPgDbDeleteConfirmId == db.Id) + { + + + + } + @if (showDumpFormForDbId == db.Id) + { + + + + } + @if (showMigrateFormForDbId == db.Id) + { + + + + } + @if (regPgCredDbId == db.Id && regPgCredentials is not null) + { + + + + } + @if (!string.IsNullOrEmpty(regPgDbActionError) && regPgDbActionErrorId == db.Id) + { + + + + } + } + +
DatabaseOwner RoleStatusActions
@db.Name@db.Owner + @switch (db.Status) + { + case RegisteredPostgresDatabaseStatus.Ready: + Ready + break; + case RegisteredPostgresDatabaseStatus.Creating: + Creating + break; + case RegisteredPostgresDatabaseStatus.Deleting: + Deleting + break; + default: + Failed + break; + } + +
+ + + + + + +
+
+
+ + Remove @db.Name? +
+ + + +
+
+
+
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(dumpError) && dumpErrorDbId == db.Id) + { +
@dumpError
+ } + @if (!string.IsNullOrEmpty(dumpSuccess) && dumpErrorDbId == db.Id) + { +
@dumpSuccess
+ } +
+
+
+ + +
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(migrateError) && migrateErrorDbId == db.Id) + { +
@migrateError
+ } + @if (!string.IsNullOrEmpty(migrateSuccess) && migrateErrorDbId == db.Id) + { +
@migrateSuccess
+ } +
+
+ @foreach (KeyValuePair cred in regPgCredentials) + { +
+ @cred.Key: @cred.Value +
+ } +
+ +
+
@regPgDbActionError
+
+
+ } + + @* ── Dump History ── *@ + @if (loadingDumps) + { +
+ + Loading dump history... +
+ } + else if (dumpsByDatabase is not null) + { + @foreach (RegisteredPostgresDatabase dumpDb in rpi.Databases) + { + @if (dumpsByDatabase.TryGetValue(dumpDb.Id, out List? dumps) && dumps.Count > 0) + { +
+
+ Dumps — @dumpDb.Name +
+
+ + + + + + + + + + + @foreach (RegisteredPostgresDump dump in dumps) + { + + + + + + + @if (showRestoreFormForDumpId == dump.Id) + { + + + + } + } + +
CreatedSizeBucketActions
@dump.CreatedAt.ToString("yyyy-MM-dd HH:mm")@FormatBytes(dump.SizeBytes)@dump.StorageLink.Name +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(dumpRestoreError) && dumpRestoreErrorDumpId == dump.Id) + { +
@dumpRestoreError
+ } + @if (!string.IsNullOrEmpty(dumpRestoreSuccess) && dumpRestoreErrorDumpId == dump.Id) + { +
@dumpRestoreSuccess
+ } +
+
+
+ } + } + } +
+ }
} - - } + } + @* ── Managed CNPG Clusters ── *@ @if (operatorStatus!.CnpgAvailable) @@ -204,9 +775,12 @@ else { @FilteredManagedClusters.Count } - + @* ── Create Cluster Form ── *@ @@ -240,6 +814,16 @@ else } +
+ + +
-
+
-
+
+
+ + +
@@ -299,6 +887,65 @@ else
} + @* ── Import Existing CNPG Cluster Form ── *@ + @if (showImportCnpg) + { +
+
+
Import Existing CNPG Cluster
+ +
+
+

Register a CloudNativePG cluster that is already running in Kubernetes. EntKube will read its configuration from the Cluster CRD and store the superuser credentials in vault. No Kubernetes resources are created or modified.

+
+
+ + +
+
+ + +
Kubernetes resource name (metadata.name)
+
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(importCnpgError)) + { +
+ @importCnpgError +
+ } +
+
+ } + @* ── Managed Cluster Cards ── *@ @if (FilteredManagedClusters is null) { @@ -306,7 +953,7 @@ else
} - else if (FilteredManagedClusters.Count == 0 && !showCreate) + else if (FilteredManagedClusters.Count == 0 && !showCreate && !showImportCnpg) {
@@ -328,6 +975,12 @@ else @mc.KubernetesCluster.Environment.Name + @if (mc.IsExternal) + { + + External + + } @if (mc.Databases.Count > 0) { @mc.Databases.Count db(s) @@ -338,6 +991,10 @@ else @onclick="() => ShowAddDatabase(mc)"> + @if (mc.StorageLinkId.HasValue) { +
+ @* ── CNPG Delete Confirmation ── *@ + @if (cnpgDeleteConfirmId == mc.Id) + { +
+
+ +
+ Delete @mc.Name? + @if (mc.IsExternal) + { + This is an externally registered cluster. + } + else + { + This will remove all Kubernetes resources and cannot be undone. + } +
+
+ + @if (mc.IsExternal) + { + + } + +
+
+
+ } + + @* ── Cluster-level operation feedback ── *@ + @if (clusterMessages.TryGetValue(mc.Id, out string? clusterMsg)) + { +
+
+ @clusterMsg +
+
+ } + @* ── Expanded Detail Panel ── *@ @if (expandedClusterId == mc.Id) { @@ -589,12 +1305,63 @@ else } - + @if (dbOwnershipOk.TryGetValue(cdb.Id, out bool ownerOk) && ownerOk) + { + + } + else + { + + } + + @if (cnpgDbDeleteConfirmId == cdb.Id) + { + + +
+ + Drop @cdb.Name? This drops the PostgreSQL database and removes all credentials. +
+ + +
+
+ + + } + @if (dbFixMessageId == cdb.Id && dbFixMessage is not null) + { + + +
+ @dbFixMessage +
+ + + } @if (credentialsDatabaseId == cdb.Id && databaseCredentials is not null) { @@ -629,9 +1396,31 @@ else } @* ── Backups ── *@ -
+
Backups - @clusterDetail.Backups.Count + @clusterDetail.Backups.Count + @if (mc.StorageLinkId.HasValue) + { + + @if (!string.IsNullOrWhiteSpace(mc.BackupSchedule)) + { + @mc.BackupSchedule + } + @mc.RetentionDays d + max @mc.MaxBackups + + + }
@if (clusterDetail.Backups.Count > 0) { @@ -785,15 +1574,52 @@ else @if (configuringScheduleId == mc.Id) { -
- - - +
+
+ Backup Settings + +
+
+
+
+ + +
Leave blank to disable scheduled backups.
+
+
+ + +
Barman WAL/data retention window.
+
+
+ + +
Backup CRs to keep.
+
+
+ +
+
+ @if (scheduleError is not null) + { +
@scheduleError
+ } + @if (scheduleSuccess is not null) + { +
@scheduleSuccess
+ } +
} } @@ -871,6 +1697,90 @@ else
} + @* ── Import Existing Database Form ── *@ + @if (importDbCluster is not null) + { +
+
+
Import Existing Database into @importDbCluster.Name
+ +
+
+

Register a database that already exists in this CNPG cluster. Click Discover to load the list of databases, or fill in the fields manually. Credentials are stored in vault — no SQL is executed.

+
+ + @if (discoveredDbs is not null) + { + @discoveredDbs.Count database(s) found + } + @if (!string.IsNullOrEmpty(discoverDbError)) + { + @discoverDbError + } +
+
+
+ + @if (discoveredDbs is { Count: > 0 }) + { + + } + else + { + + } +
+
+ + +
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(importDbError)) + { +
+ @importDbError +
+ } +
+
+ } + @* ── Restore Form ── *@ @if (restoreSource is not null) { @@ -939,91 +1849,21 @@ else } - @* ── MongoDB Clusters ── *@ - @if (operatorStatus.MongoDbAvailable) + @* ── Prometheus Metrics for expanded CNPG cluster ── *@ + @if (expandedClusterId.HasValue && managedClusters is not null) { -
-
- MongoDB Clusters - @if (FilteredMongoClusters is not null) - { - @FilteredMongoClusters.Count - } -
- - @if (FilteredMongoClusters is null) - { -
-
+ CnpgCluster? expandedCnpg = managedClusters.FirstOrDefault(c => c.Id == expandedClusterId.Value); + @if (expandedCnpg is not null) + { +
+
+
Prometheus Metrics — @expandedCnpg.Name
- } - else if (FilteredMongoClusters.Count == 0) - { -
- - No MongoDB clusters discovered on Kubernetes. +
+
- } - else - { -
- @foreach (DatabaseClusterInfo dbCluster in FilteredMongoClusters) - { -
-
-
-
- - @dbCluster.Name -
- @(GetStatusBadge(dbCluster.Status)) -
-
-
-
- @dbCluster.EnvironmentName -
-
- @dbCluster.ClusterName -
-
- @dbCluster.Namespace -
-
- @dbCluster.Instances member(s) -
- @if (dbCluster.Version is not null) - { -
- v@dbCluster.Version -
- } -
- - @if (dbCluster.Databases.Count > 0) - { -
-
- Databases: - @foreach (string dbName in dbCluster.Databases) - { - @dbName - } -
- } -
- @if (dbCluster.CreatedAt.HasValue) - { - - } -
-
- } -
- } -
+
+ } } @* ── Managed MongoDB Clusters ── *@ @@ -1036,11 +1876,82 @@ else { @FilteredManagedMongoClusters.Count } - +
+ + +
+ @* ── Register Existing MongoDB Cluster Form ── *@ + @if (showMongoRegister) + { +
+
+
Register Existing MongoDB Cluster
+ +
+
+

Register a MongoDB cluster that was deployed outside EntKube. This creates a tracking record without modifying the live cluster.

+
+
+ + +
Must match the MongoDBCommunity CR name in Kubernetes (if using community operator)
+
+
+ + +
+
+ + +
+
+
+ + After registering, use the settings button to store the admin password as a K8s Secret — this enables S3 backup and migration. + Version and databases are discovered automatically for community-operator clusters. +
+
+
+
+ + +
+ @if (!string.IsNullOrEmpty(mongoRegisterError)) + { +
+ @mongoRegisterError +
+ } +
+
+ } + @* ── Create MongoDB Cluster Form ── *@ @if (showMongoCreate) { @@ -1072,6 +1983,21 @@ else }
+
+ + + + +
-
+
-
+
+
+ + +
} - else if (FilteredManagedMongoClusters.Count == 0 && !showMongoCreate) + else if (FilteredManagedMongoClusters.Count == 0 && !showMongoCreate && !showMongoRegister) {
- No managed MongoDB clusters yet. Click "Create Cluster" to provision a new MongoDB replica set. + No managed MongoDB clusters yet. Click "Create Cluster" to provision a new one, or "Register Existing" to track a cluster deployed outside EntKube.
} else @@ -1160,40 +2090,109 @@ else @mc.KubernetesCluster.Environment.Name + @if (mc.IsExternal) + { + + External + + @if (mc.StorageLinkId.HasValue) + { + + S3 + + } + } @if (mc.Databases.Count > 0) { @mc.Databases.Count db(s) }
- - @if (mc.StorageLinkId.HasValue) + @if (mc.IsExternal) { - - + @if (mc.StorageLinkId.HasValue) + { + + + } + + + + } + else + { + + @if (mc.StorageLinkId.HasValue) + { + + + + } + + + } -
+ @* ── Delete / Unregister Confirmation ── *@ + @if (mongoDeleteConfirmId == mc.Id) + { +
+
+ +
+ Delete @mc.Name? + @if (mc.IsExternal) + { + This is an externally registered cluster. + } + else + { + This will delete all Kubernetes resources for this cluster. This cannot be undone. + } +
+
+ + @if (mc.IsExternal) + { + + } + +
+
+ @if (!string.IsNullOrEmpty(mongoCreateError) && mongoDeletingId == null) + { +
@mongoCreateError
+ } +
+ } + + @* ── External Cluster Settings Panel ── *@ + @if (mc.IsExternal && mongoExtSettingsClusterId == mc.Id) + { +
+
External Cluster — Credentials & Backup Settings
+
+
+ + +
+ Stored as a K8s Secret on the cluster — never in EntKube's database. + The connection endpoint is derived automatically from the cluster name and namespace, exactly like managed clusters. +
+
+
+ + +
+
+ + +
Leave blank for on-demand only
+
+
+ + +
+
+ + +
+
+
+ + +
+ @if (!string.IsNullOrEmpty(mongoExtError) && mongoExtSettingsClusterId == mc.Id) + { +
@mongoExtError
+ } + @if (!string.IsNullOrEmpty(mongoExtSuccess) && mongoExtSettingsClusterId == mc.Id) + { +
@mongoExtSuccess
+ } +
+ } + + @* ── Track External Database Form ── *@ + @if (mc.IsExternal && mongoTrackDbClusterId == mc.Id) + { +
+
Track Existing Database
+

Register a database that already exists in this external cluster. No MongoDB commands are run — this only creates a tracking record.

+
+
+ + +
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(mongoTrackDbError) && mongoTrackDbClusterId == mc.Id) + { +
@mongoTrackDbError
+ } +
+ } + + @* ── Migrate External → Managed Form ── *@ + @if (mc.IsExternal && mongoMigrateSourceClusterId == mc.Id) + { +
+
Migrate to Managed Cluster
+

+ Copies all data from @mc.Name (via connection URI) to a managed Community Operator cluster using a direct dump-restore Job. + The migration runs on the target cluster's Kubernetes environment. Existing data in the target is overwritten (mongorestore --drop). +

+ @{ + var managedMongoTargets = managedMongoClusters?.Where(c => !c.IsExternal).ToList() ?? []; + } + @if (managedMongoTargets.Count == 0) + { +
No managed MongoDB clusters available. Create one first.
+ } + else + { +
+
+ + +
+
+ + +
+
+ } + @if (!string.IsNullOrEmpty(mongoMigrateError) && mongoMigrateSourceClusterId == mc.Id) + { +
@mongoMigrateError
+ } + @if (!string.IsNullOrEmpty(mongoMigrateSuccess) && mongoMigrateSourceClusterId == mc.Id) + { +
@mongoMigrateSuccess
+ } +
+ } + + @* ── Atlas Restore Form ── *@ + @if (atlasRestoreClusterId == mc.Id) + { +
+
Restore from MongoDB Atlas
+
+
+ + +
Include credentials in the URI. The string is stored as a short-lived K8s Secret and never persisted in EntKube.
+
+
+ + +
+
+ + +
Defaults to the same name as source. Existing data in the target database will be dropped.
+
+
+
+ + +
+ @if (!string.IsNullOrEmpty(atlasRestoreError)) + { +
+ @atlasRestoreError +
+ } + @if (!string.IsNullOrEmpty(atlasRestoreSuccess)) + { +
+ @atlasRestoreSuccess +
+ } +
+ } + @* ── Expanded Mongo Detail Panel ── *@ @if (expandedMongoClusterId == mc.Id) { @@ -1377,27 +2609,186 @@ else } @mdb.CreatedAt.ToString("yyyy-MM-dd HH:mm") - - + + + } + + + @if (mongoCredDbId == mdb.Id && mongoDbCredentials is not null) + { + + +
+ + + @foreach (KeyValuePair cred in mongoDbCredentials) + { + + + + + } + +
@cred.Key@cred.Value
+
+ + + } + @if (mongoDbActionErrorId == mdb.Id && !string.IsNullOrEmpty(mongoDbActionError)) + { + + +
@mongoDbActionError
+ + + } + @if (mongoMigrateDbId == mdb.Id) + { + + +
+
+ + +
+
+ + +
+ + +
+ @if (!string.IsNullOrEmpty(mongoMigrateDbError) && mongoMigrateDbId == mdb.Id) + { +
@mongoMigrateDbError
+ } + @if (!string.IsNullOrEmpty(mongoMigrateDbSuccess) && mongoMigrateDbId == mdb.Id) + { +
@mongoMigrateDbSuccess
+ } + + + } + @if (mongoDbDeleteConfirmId == mdb.Id) + { + + +
+ + Delete @mdb.Name? +
+ + @if (mc.IsExternal) + { + + } + +
+
+ + + } } } + else if (mc.IsExternal) + { +

No databases tracked yet. Use the button to track an existing database, or databases are discovered automatically if this cluster uses the MongoDB Community Operator.

+ } else {

No databases created yet. Use the button to add one.

} @* ── Backups ── *@ -
+
Backups - @mongoClusterDetail.Backups.Count + @mongoClusterDetail.Backups.Count + @if (mc.StorageLinkId.HasValue) + { + + @if (!string.IsNullOrWhiteSpace(mc.BackupSchedule)) { @mc.BackupSchedule } + @mc.RetentionDays d + max @mc.MaxBackups + + + }
@if (mongoClusterDetail.Backups.Count > 0) { @@ -1455,6 +2846,12 @@ else {

No backups yet. Click to create one.

} + else if (mc.IsExternal) + { +

+ Backups are not configured. Set an admin password and assign an S3 bucket via the settings button to enable backups. +

+ } else {

Backups are not configured. Assign an S3 bucket to enable backups.

@@ -1548,8 +2945,66 @@ else } + + @* ── Backup Settings Panel ── *@ + @if (mongoConfiguringScheduleId == mc.Id) + { +
+
+ Backup Settings + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ @if (!string.IsNullOrEmpty(mongoScheduleError)) + { +
@mongoScheduleError
+ } + @if (!string.IsNullOrEmpty(mongoScheduleSuccess)) + { +
@mongoScheduleSuccess
+ } +
+
+ } } + @if (mongoClusterMessages.TryGetValue(mc.Id, out string? mongoMsg)) + { +
@mongoMsg
+ } @if (!string.IsNullOrEmpty(mc.LastError)) {
@@ -1668,6 +3123,66 @@ else
} + @* ── MongoDB Resize Form ── *@ + @if (mongoResizeCluster is not null) + { +
+
+
Resize @mongoResizeCluster.Name
+
+
+
+ + Storage — patches each PVC directly. Requires allowVolumeExpansion: true on the StorageClass. Can only grow, not shrink.
+ CPU / Memory — re-applies the manifest; the operator performs a rolling restart. +
+
+
+ + +
Leave blank to keep current (@mongoResizeCluster.StorageSize)
+
+
+ + +
Current: @(mongoResizeCluster.CpuRequest ?? "—")
+
+
+ + +
Current: @(mongoResizeCluster.CpuLimit ?? "—")
+
+
+ + +
Current: @(mongoResizeCluster.MemoryRequest ?? "—")
+
+
+ + +
Current: @(mongoResizeCluster.MemoryLimit ?? "—")
+
+
+ + +
+
+ @if (!string.IsNullOrEmpty(mongoResizeError)) + { +
@mongoResizeError
+ } +
+
+ } + @* ── MongoDB Upgrade Form ── *@ @if (mongoUpgradeCluster is not null) { @@ -1676,14 +3191,34 @@ else
Upgrade @mongoUpgradeCluster.Name (currently @mongoUpgradeCluster.MongoVersion)
+ @if (mongoUpgradeCluster.IsExternal) + { +
+ + This patches spec.version in the existing MongoDBCommunity CRD. The Community Operator performs a rolling upgrade of replica set members. + Existing users, auth config, and StatefulSet settings are preserved. +
+ }
- + + + +
Enter any X.Y.Z version supported by the MongoDB Community Operator
+
+
+ + @* ── Add repo form ── *@ + @if (showAddForm) + { +
+
+
Register Git Repository
+ + @if (addError is not null) + { +
+ @addError +
+ } + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ @if (newAuthType == GitAuthType.HttpsPassword) + { +
+ + +
+ } +
+ +
+ + +
+
+
+ } + + @* ── Repo list ── *@ + @if (repos is null) + { +
+
+
+ } + else if (repos.Count == 0 && !showAddForm) + { +
+ +

No repositories registered. Add one to enable Git-sourced deployments.

+
+ } + else + { + + + + + + + + + + + + + @foreach (GitRepository repo in repos) + { + + + + + + + + + + @* ── Credential entry panel (expanded inline) ── *@ + @if (credentialRepoId == repo.Id) + { + + + @if (scope == "cnpg") + { + + } @@ -205,6 +262,19 @@ else { + @if (scope == "cnpg") + { + + }
NameURLAuthBranchConnection
@repo.Name + @TruncateUrl(repo.Url) + @AuthLabel(repo.AuthType)@repo.DefaultBranch + @if (testingRepoId == repo.Id) + { + + } + else if (testResults.TryGetValue(repo.Id, out var result)) + { + @if (result.Success) + { + + @result.Message + + } + else + { + + Failed + + } + } + else + { + + } + +
+ + + @if (confirmDeleteRepoId != repo.Id) + { + + } + else + { + + + } +
+
+
+
+ Credentials — @repo.Name +
+ + @if (credError is not null) + { +
@credError
+ } + @if (credSuccess is not null) + { +
+ @credSuccess +
+ } + + @if (repo.AuthType == GitAuthType.HttpsPat) + { +
+
+ + +
Stored encrypted in the tenant vault.
+
+
+ } + else if (repo.AuthType == GitAuthType.HttpsPassword) + { +
+
+ + +
Username: @repo.Username — stored encrypted in the tenant vault.
+
+
+ } + else if (repo.AuthType == GitAuthType.SshKey) + { +
+
+ + +
+ + @if (themeSaved) + { + Saved + } + @if (themeError is not null) + { + @themeError + } +
+
- -
-
- - @if (themeSaved) - { - Saved - } - @if (themeError is not null) - { - @themeError - } -
+ } + else if (namedThemes.Count == 0 && !showCreateTheme) + { +
+ No themes yet. Create a named theme to save CSS overrides and native theme settings in one bundle. +
+ } } } + else if (instanceTab == "migrate") + { + @* ── Migrate tab ── *@ +
+
+ @if (migrateError is not null) + { +
@migrateError
+ } + @if (migrateResult is not null) + { + bool anyErrors = migrateResult.Errors.Count > 0; + bool nothingCopied = migrateCopyUsers + ? migrateResult.Copied == 0 && migrateResult.Skipped == 0 && !anyErrors + : false; + string alertClass = anyErrors ? "alert-warning" + : nothingCopied ? "alert-warning" + : "alert-success"; + +
+ @if (migrateCopyUsers) + { + @if (migrateResult.Copied > 0) + { +
+ Users: @migrateResult.Copied copied@(migrateResult.Skipped > 0 ? $", {migrateResult.Skipped} skipped (already exist)." : ".")
+ } + else if (migrateResult.Skipped > 0) + { +
+ Users: 0 copied — all @migrateResult.Skipped already exist in target.
+ } + else if (!anyErrors) + { +
+ No users found in the source realm.
+ } + } + @if (migrateCopyThemeCss) + { + @if (migrateCssCopied > 0) + { +
+ Theme CSS: @migrateCssCopied / 4 types copied to target. Edit them in the Themes tab.
+ } + else + { +
+ Theme CSS: nothing to copy — the source instance has no CSS saved in EntKube yet. + EntKube stores CSS overrides separately from the themes installed in Keycloak itself. + Open the Themes tab on the source instance to add CSS, then copy again.
+ } + } + @if (anyErrors) + { +
    + @foreach (string err in migrateResult.Errors) + { +
  • @err
  • + } +
+ } +
+ } + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + @if (migrateCopyUsers) + { +
+ + @if (loadingMigrateSourceRealms) + { +
+ } + else + { + + } +
+
+ + +
+ } + +
+ +
+
+
+
+ } else { @* Realms list *@ @@ -858,18 +1196,70 @@ else if (selectedKeycloak is not null) else { @* ── Level 1: Keycloak instance list ── *@ +
+ +
+ + @if (showRegisterExternal) + { +
+
Register Externally Deployed Keycloak
+
+ @if (registerExternalError is not null) + { +
@registerExternalError
+ } +

+ Connect a Keycloak instance that was deployed outside of EntKube. + Admin credentials are stored in EntKube's vault and are only used to call the Keycloak Admin API. +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ } + @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. + Install the Keycloak component from the Components tab, or register an external instance above.
} else {

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

@@ -881,11 +1271,17 @@ else
-
@kc.Component.Name
- @kc.Component.Cluster.Name +
@kc.DisplayName
+ @(kc.ClusterDisplayName ?? "")
- @if (kc.Component.Status == ComponentStatus.Failed) + @if (kc.IsExternal) + { + + External + + } + @if (kc.Component?.Status == ComponentStatus.Failed) { Install failed @@ -916,6 +1312,13 @@ else @kc.Config.CnpgDatabase.CnpgCluster.Name / @kc.Config.CnpgDatabase.Name
} + else if (kc.Config?.RegisteredPostgresDatabase is not null) + { +
+ + @kc.Config.RegisteredPostgresDatabase.RegisteredPostgresInstance.Name / @kc.Config.RegisteredPostgresDatabase.Name +
+ }
+ +@if (isLoading) +{ +
Loading incidents…
+} +else if (incidents.Count == 0) +{ +
+ + No incidents found for the selected filters. +
+} +else +{ +
+ + + + + + + + + + + + + + @foreach (AlertIncident incident in incidents) + { + bool expanded = expandedId == incident.Id; + + + + + + + + + + @if (expanded) + { + + + + } + } + +
SeverityAlertClusterStartedDurationStatusActions
@SeverityBadge(incident.Severity)@incident.AlertName@incident.Cluster?.Name@incident.StartsAt.ToLocalTime().ToString("MMM d HH:mm")@FormatDuration(incident)@StatusBadge(incident.Status) + @if (incident.Status == IncidentStatus.Active) + { + + } + @if (incident.Status != IncidentStatus.Resolved) + { + + } + +
+ @if (!string.IsNullOrEmpty(incident.Summary)) + { +

Summary: @incident.Summary

+ } + @if (!string.IsNullOrEmpty(incident.Description)) + { +

@incident.Description

+ } + @if (incident.AcknowledgedBy is not null) + { +

+ Acknowledged by @incident.AcknowledgedBy + at @incident.AcknowledgedAt?.ToLocalTime().ToString("MMM d HH:mm") +

+ } + + @if (incident.Notes.Count > 0) + { +
+ Notes + @foreach (IncidentNote note in incident.Notes) + { +
+
@note.Author · @note.CreatedAt.ToLocalTime().ToString("MMM d HH:mm")
+
@note.Content
+
+ } +
+ } + +
+ + +
+
+
+} + +@code { + [Parameter] public required Guid TenantId { get; set; } + + private List incidents = []; + private List clusters = []; + private AlertStats? stats; + private Guid? expandedId; + private string filterStatus = ""; + private string filterSeverity = ""; + private string filterClusterId = ""; + private int filterDays = 7; + private string noteText = ""; + private bool isLoading = true; + private string? currentUser; + + protected override async Task OnInitializedAsync() + { + AuthenticationState authState = await AuthStateProvider.GetAuthenticationStateAsync(); + currentUser = authState.User.Identity?.Name ?? "Unknown"; + await Task.WhenAll(LoadIncidents(), LoadStats()); + } + + private async Task LoadStats() + { + stats = await IncidentService.GetStatsForTenantAsync(TenantId, windowDays: 30); + } + + private async Task LoadIncidents() + { + isLoading = true; + StateHasChanged(); + + IncidentStatus? status = filterStatus switch + { + "Active" => IncidentStatus.Active, + "Acknowledged" => IncidentStatus.Acknowledged, + "Resolved" => IncidentStatus.Resolved, + _ => null + }; + + Guid? clusterId = Guid.TryParse(filterClusterId, out Guid cid) ? cid : null; + + incidents = await IncidentService.GetIncidentsForTenantAsync( + TenantId, + status: status, + severity: string.IsNullOrEmpty(filterSeverity) ? null : filterSeverity, + clusterId: clusterId, + from: DateTime.UtcNow.AddDays(-filterDays), + to: null); + + // Collect unique clusters from results + clusters = incidents + .Where(i => i.Cluster is not null) + .Select(i => i.Cluster!) + .DistinctBy(c => c.Id) + .OrderBy(c => c.Name) + .ToList(); + + isLoading = false; + } + + private void ToggleExpand(Guid id) + { + expandedId = expandedId == id ? null : id; + noteText = ""; + } + + private async Task Acknowledge(AlertIncident incident) + { + await IncidentService.AcknowledgeAsync(incident.Id, currentUser!); + await LoadIncidents(); + } + + private async Task Resolve(AlertIncident incident) + { + await IncidentService.ResolveAsync(incident.Id, currentUser!); + await LoadIncidents(); + } + + private async Task AddNote(AlertIncident incident) + { + if (string.IsNullOrWhiteSpace(noteText)) return; + await IncidentService.AddNoteAsync(incident.Id, currentUser!, noteText.Trim()); + noteText = ""; + await LoadIncidents(); + expandedId = incident.Id; + } + + private static string FormatDuration(AlertIncident incident) + { + DateTime end = incident.EndsAt ?? DateTime.UtcNow; + TimeSpan duration = end - incident.StartsAt; + if (duration.TotalDays >= 1) return $"{(int)duration.TotalDays}d {duration.Hours}h"; + if (duration.TotalHours >= 1) return $"{(int)duration.TotalHours}h {duration.Minutes}m"; + return $"{(int)duration.TotalMinutes}m"; + } + + private static RenderFragment SeverityBadge(string severity) => severity switch + { + "critical" => @Critical, + "warning" => @Warning, + "info" => @Info, + _ => @@severity + }; + + private static RenderFragment StatusBadge(IncidentStatus status) => status switch + { + IncidentStatus.Active => @Active, + IncidentStatus.Acknowledged => @Acknowledged, + IncidentStatus.Resolved => @Resolved, + _ => @@status + }; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/LogBrowser.razor b/src/EntKube.Web/Components/Pages/Tenants/LogBrowser.razor new file mode 100644 index 0000000..bf84917 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/LogBrowser.razor @@ -0,0 +1,222 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject LokiService LokiService + +@if (!lokiAvailable && !isLoading) +{ +
+ + Loki is not installed or configured on this cluster. Deploy a Loki component to enable log browsing. +
+} +else +{ + @* ── Controls ── *@ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + @if (!string.IsNullOrEmpty(queryError)) + { +
+ @queryError +
+ } + + @* ── Log output ── *@ + @if (allEntries.Count > 0) + { +
+ @allEntries.Count entries · @queried.ToLocalTime().ToString("HH:mm:ss") +
+ + +
+
+ +
+ @foreach (LokiLogEntry entry in FilteredEntries) + { +
+ + @entry.Timestamp.ToLocalTime().ToString("HH:mm:ss.fff") + + @entry.Line +
+ } +
+ } + else if (hasQueried && !isQuerying) + { +
+ + No logs found for the selected criteria. +
+ } +} + +@code { + [Parameter] public required Guid ClusterId { get; set; } + [Parameter] public string? PresetNamespace { get; set; } + [Parameter] public string? PresetPod { get; set; } + + private List namespaces = []; + private List pods = []; + private List allEntries = []; + private string selectedNamespace = ""; + private string selectedPod = ""; + private string textFilter = ""; + private string levelFilter = ""; + private int timeRangeMinutes = 60; + private int limit = 200; + private bool lokiAvailable; + private bool isLoading = true; + private bool isQuerying; + private bool hasQueried; + private string? queryError; + private DateTime queried; + + private IEnumerable FilteredEntries => allEntries.Where(e => levelFilter switch + { + "Error" => e.DetectedLevel >= LogLevel.Error, + "Warn" => e.DetectedLevel >= LogLevel.Warn, + "Info" => e.DetectedLevel >= LogLevel.Info, + _ => true + }); + + protected override async Task OnParametersSetAsync() + { + isLoading = true; + lokiAvailable = await LokiService.IsAvailableAsync(ClusterId); + + if (lokiAvailable) + { + KubernetesOperationResult> nsResult = await LokiService.GetNamespacesAsync(ClusterId); + if (nsResult.IsSuccess) namespaces = nsResult.Data ?? []; + + if (!string.IsNullOrEmpty(PresetNamespace)) + { + selectedNamespace = PresetNamespace; + await OnNamespaceChanged(); + } + if (!string.IsNullOrEmpty(PresetPod)) + selectedPod = PresetPod; + } + isLoading = false; + } + + private async Task OnNamespaceChanged() + { + pods = []; + selectedPod = ""; + if (string.IsNullOrEmpty(selectedNamespace)) return; + + KubernetesOperationResult> podResult = + await LokiService.GetPodsAsync(ClusterId, selectedNamespace); + if (podResult.IsSuccess) pods = podResult.Data ?? []; + } + + private async Task Search() + { + if (string.IsNullOrEmpty(selectedNamespace)) return; + isQuerying = true; + queryError = null; + allEntries = []; + StateHasChanged(); + + DateTime to = DateTime.UtcNow; + DateTime from = to.AddMinutes(-timeRangeMinutes); + + KubernetesOperationResult> result = + await LokiService.QueryRangeAsync( + ClusterId, selectedNamespace, + string.IsNullOrEmpty(selectedPod) ? null : selectedPod, + null, + string.IsNullOrEmpty(textFilter) ? null : textFilter, + from, to, limit); + + if (result.IsSuccess && result.Data is not null) + { + allEntries = result.Data + .SelectMany(s => s.Entries) + .OrderBy(e => e.Timestamp) + .ToList(); + } + else + { + queryError = result.Error; + } + + queried = DateTime.UtcNow; + hasQueried = true; + isQuerying = false; + } + + private static string LineStyle(LogLevel level) => level switch + { + LogLevel.Fatal or LogLevel.Error => "color:#f48771", + LogLevel.Warn => "color:#cca700", + LogLevel.Info => "color:#9cdcfe", + LogLevel.Debug => "color:#808080", + _ => "color:#d4d4d4" + }; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/MaintenanceWindows.razor b/src/EntKube.Web/Components/Pages/Tenants/MaintenanceWindows.razor new file mode 100644 index 0000000..76eda61 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/MaintenanceWindows.razor @@ -0,0 +1,213 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.EntityFrameworkCore +@inject IncidentService IncidentService +@inject IDbContextFactory DbFactory +@inject AuthenticationStateProvider AuthStateProvider + +
+
Maintenance Windows
+ +
+ +@if (activeWindows.Count > 0) +{ +
+ +
+ Active maintenance: + @foreach (MaintenanceWindow w in activeWindows) + { + @w.Title (until @w.EndsAt.ToLocalTime().ToString("HH:mm")) + } +
Alert notifications are suppressed for covered clusters.
+
+
+} + +@if (windows.Count == 0) +{ +
+ + No maintenance windows scheduled. +
+} +else +{ +
+ + + + + + + + + + + + + + @foreach (MaintenanceWindow w in windows) + { + DateTime now = DateTime.UtcNow; + bool active = w.StartsAt <= now && w.EndsAt >= now; + bool past = w.EndsAt < now; + + + + + + + + + + } + +
TitleClusterStartsEndsCreated ByStatus
@w.Title@(w.Cluster?.Name ?? "All clusters")@w.StartsAt.ToLocalTime().ToString("MMM d HH:mm")@w.EndsAt.ToLocalTime().ToString("MMM d HH:mm")@w.CreatedBy + @if (active) + { + Active + } + else if (past) + { + Completed + } + else + { + Scheduled + } + + @if (!past) + { + + } +
+
+} + +@if (showAdd) +{ +
+
+
Schedule Maintenance Window
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(formError)) + { +
@formError
+ } +
+ + +
+
+
+} + +@code { + [Parameter] public required Guid TenantId { get; set; } + + private List windows = []; + private List activeWindows = []; + private List clusters = []; + private bool showAdd; + private string newTitle = ""; + private string newDescription = ""; + private string newClusterId = ""; + private DateTime newStartsAt = DateTime.Now.AddMinutes(5); + private DateTime newEndsAt = DateTime.Now.AddHours(2); + private string? formError; + private string? currentUser; + + protected override async Task OnInitializedAsync() + { + AuthenticationState auth = await AuthStateProvider.GetAuthenticationStateAsync(); + currentUser = auth.User.Identity?.Name ?? "Unknown"; + await Load(); + } + + private async Task Load() + { + windows = await IncidentService.GetMaintenanceWindowsAsync(TenantId); + DateTime now = DateTime.UtcNow; + activeWindows = windows.Where(w => w.StartsAt <= now && w.EndsAt >= now).ToList(); + + using ApplicationDbContext db = DbFactory.CreateDbContext(); + clusters = await db.KubernetesClusters + .Where(c => c.TenantId == TenantId) + .OrderBy(c => c.Name) + .ToListAsync(); + } + + private void ShowAdd() + { + showAdd = true; + newTitle = ""; + newDescription = ""; + newClusterId = ""; + newStartsAt = DateTime.Now.AddMinutes(5); + newEndsAt = DateTime.Now.AddHours(2); + formError = null; + } + + private async Task Save() + { + formError = null; + if (string.IsNullOrWhiteSpace(newTitle)) { formError = "Title is required."; return; } + if (newEndsAt <= newStartsAt) { formError = "End time must be after start time."; return; } + + Guid? clusterId = Guid.TryParse(newClusterId, out Guid cid) ? cid : null; + + await IncidentService.CreateMaintenanceWindowAsync( + TenantId, + newTitle.Trim(), + string.IsNullOrWhiteSpace(newDescription) ? null : newDescription.Trim(), + clusterId, + newStartsAt.ToUniversalTime(), + newEndsAt.ToUniversalTime(), + currentUser!); + + showAdd = false; + await Load(); + } + + private async Task Delete(Guid id) + { + await IncidentService.DeleteMaintenanceWindowAsync(id); + await Load(); + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/MessagingTab.razor b/src/EntKube.Web/Components/Pages/Tenants/MessagingTab.razor new file mode 100644 index 0000000..72d869f --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/MessagingTab.razor @@ -0,0 +1,1509 @@ +@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"; + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/NetworkingTab.razor b/src/EntKube.Web/Components/Pages/Tenants/NetworkingTab.razor new file mode 100644 index 0000000..30d280f --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/NetworkingTab.razor @@ -0,0 +1,1284 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject VpnService VpnService +@inject TenantService TenantService + +@* ============================================================================ + Networking Tab — manages site-to-site IPsec tunnels (StrongSwan) and + cluster mesh connections (Submariner) for the tenant. Tunnels are + tenant-scoped, multi-cluster aware, and upgraded via the standard + Helm component flow — no separate ops process needed. + ============================================================================ *@ + +@if (loading) +{ +
+
+

Loading VPN tunnels...

+
+} +else +{ + @* ── Header ── *@ +
+
+
VPN Tunnels
+

+ Site-to-site IPsec (StrongSwan) and cluster mesh (Submariner). Gateway components are upgraded via the cluster's Components tab. +

+
+ @if (!showAddForm) + { + + } +
+ + @* ── Error banner ── *@ + @if (errorMessage is not null) + { +
+ @errorMessage + +
+ } + + @* ── Add Tunnel Wizard ── *@ + @if (showAddForm) + { +
+
+ New VPN Tunnel — Step @wizardStep of @(newTunnelType == VpnTunnelType.SiteToSite ? 4 : 3) + +
+
+ + @* Step 1: Name + Type *@ + @if (wizardStep == 1) + { +
+ + +
+
+ + +
+ +
+
+
+
+
+ + Site-to-Site IPsec +
+

+ Connect this cluster to an external network (customer data centre, cloud VGW). + Uses StrongSwan with IKEv2. +

+
+
+
+
+
+
+
+ + Cluster Mesh +
+

+ Connect multiple platform clusters so pods and services can reach each other. + Uses Submariner with IPsec. +

+
+
+
+
+ } + + @* Step 2: Local cluster(s) *@ + @if (wizardStep == 2) + { + @if (newTunnelType == VpnTunnelType.SiteToSite) + { +
+ +

The cluster where the StrongSwan pod will be installed.

+ +
+
+
+ + +
Pod network CIDR — routed through the VPN to the remote site.
+
+
+ + +
Service network CIDR — include if remote side needs to reach cluster services.
+
+
+ } + else + { +

+ Select all clusters that will join the mesh. + The broker host stores cluster metadata — it carries no data-plane traffic. +

+ @foreach (KubernetesCluster cluster in availableClusters) + { + bool selected = meshClusterSelections.ContainsKey(cluster.Id) && meshClusterSelections[cluster.Id].Selected; +
+
+
+ + + @if (selected && meshBrokerClusterId == cluster.Id) + { + Broker host + } +
+ @if (selected) + { +
+
+ +
+
+ +
+
+ @if (meshBrokerClusterId != cluster.Id) + { + + } +
+
+ } +
+
+ } + } + } + + @* Step 3 (SiteToSite): Remote endpoint *@ + @if (wizardStep == 3 && newTunnelType == VpnTunnelType.SiteToSite) + { +

Configure the external site that this tunnel connects to.

+
+ + +
+
+ + +
+
+ + +
Enter the networks behind the remote gateway.
+
+
+ + +
Stored encrypted in the tenant vault.
+
+ } + + @* Step 3 (ClusterMesh) or Step 4 (SiteToSite): Security profile *@ + @if ((wizardStep == 3 && newTunnelType == VpnTunnelType.ClusterMesh) || + (wizardStep == 4 && newTunnelType == VpnTunnelType.SiteToSite)) + { +

Security Profile

+
+ @foreach (SecurityProfile profile in SecurityProfiles) + { + bool active = selectedProfile == profile.Key; +
+
+
+
+ + @profile.Name + @if (profile.Recommended) + { + Recommended + } +
+

@profile.Description

+
+
Phase 1: @profile.IkeProposal
+
Phase 2: @profile.EspProposal
+
+
+
+
+ } +
+
+
+
+ + Custom +
+

Manually specify IKE and ESP proposals.

+
+
+
+
+ + @if (selectedProfile == "custom") + { + bool p1IsAead = p1Enc.Contains("gcm") || p1Enc == "chacha20poly1305"; + bool p2IsAead = p2Enc.Contains("gcm") || p2Enc == "chacha20poly1305"; + +
+
+ Phase 1 — IKE SA (key exchange & authentication) +
+
+
+
+ + +
+
+ + +
+
+ + + @if (p1IsAead) + { +
AEAD provides its own auth; this is the PRF only.
+ } +
+
+ + +
+
+
+ Proposal string: @ikeProposal +
+
+
+ +
+
+ Phase 2 — Child SA / ESP (data encryption) +
+
+
+
+ + +
+ @if (!p2IsAead) + { +
+ + +
+ } + else + { +
+ AEAD mode — integrity is built in, no separate HMAC needed. +
+ } +
+ + +
PFS generates a fresh DH key for each Child SA rekey.
+
+
+
+ Proposal string: @espProposal +
+
+
+ } + +
+
+ + +
Liveness check interval.
+
+
+ + +
Dead peer threshold.
+
+
+ + +
Phase 1 rekey interval. Default: 86400 (24h).
+
+
+ + +
Phase 2 / ESP rekey interval. Default: 3600 (1h).
+
+
+ } + + @* Wizard nav buttons *@ +
+ + @if ((newTunnelType == VpnTunnelType.SiteToSite && wizardStep < 4) || + (newTunnelType == VpnTunnelType.ClusterMesh && wizardStep < 3)) + { + + } + else + { + + } +
+
+
+ } + + @* ── Tunnel List ── *@ + @if (tunnels.Count == 0 && !showAddForm) + { +
+ +

No VPN tunnels configured.

+

Add a tunnel to connect this tenant to external networks or link your clusters together.

+
+ } + else + { + @foreach (VpnTunnel tunnel in tunnels) + { + bool expanded = expandedTunnelId == tunnel.Id; +
+
+
+
+ +
+ @tunnel.Name + @if (!string.IsNullOrWhiteSpace(tunnel.Description)) + { + — @tunnel.Description + } +
+ @GetTunnelTypeBadge(tunnel.TunnelType) + @GetStatusBadge(tunnel.Status) + + @tunnel.LocalEndpoints.Count cluster@(tunnel.LocalEndpoints.Count != 1 ? "s" : "") + + @if (tunnel.TunnelType == VpnTunnelType.SiteToSite) + { + + @tunnel.RemoteEndpoints.Count remote site@(tunnel.RemoteEndpoints.Count != 1 ? "s" : "") + + } +
+
+
+
+ @if (tunnel.LastStatusCheckAt.HasValue) + { + Checked @tunnel.LastStatusCheckAt.Value.ToString("HH:mm") + } + +
+
+ + @* ── Expanded detail ── *@ + @if (expanded) + { +
+ + @* Local endpoints *@ +
Platform Clusters
+
+ + + + + + + + + + + + + @foreach (VpnLocalEndpoint ep in tunnel.LocalEndpoints) + { + + + + + + + + + } + +
ClusterRoleSubnetsPublic IPGateway ComponentStatus
@ep.Cluster.Name@GetRoleBadge(ep.Role) + @string.Join(", ", VpnService.ParseSubnets(ep.Subnets)) + + @if (!string.IsNullOrWhiteSpace(ep.PublicIp)) + { + @ep.PublicIp + } + else + { + + } + + @if (ep.Component is not null) + { + + @ep.Component.Name + + } + else + { + Not deployed + } + @GetEndpointStatusBadge(ep.Status)
+
+ + @* Remote endpoints (SiteToSite only) *@ + @if (tunnel.TunnelType == VpnTunnelType.SiteToSite) + { +
+
Remote Sites
+ +
+ @if (tunnel.RemoteEndpoints.Count == 0) + { +

No remote sites configured. Add one to activate the tunnel.

+ } + else + { +
+ + + + + + + + + + + + + @foreach (VpnRemoteEndpoint rep in tunnel.RemoteEndpoints) + { + + + + + + + + + } + +
NameRemote IPSubnetsAuthStatus
@rep.Name@rep.PublicIp + @string.Join(", ", VpnService.ParseSubnets(rep.Subnets)) + + @rep.AuthMode + @if (rep.AuthMode == VpnAuthMode.Psk) + { + + Vault + + } + @GetConnectionStatusBadge(rep.Status) +
+ + +
+ @if (rotatePskEndpointId == rep.Id) + { +
+ + + +
+ } +
+
+ } + + @* Inline add-remote-endpoint form *@ + @if (addRemoteEndpointTunnelId == tunnel.Id) + { +
+
Add Remote Site
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Sent to this peer during IKE. Defaults to gateway's public IP.
+
+
+ + +
Reject connections if peer doesn't match.
+
+
+
+ + +
+
+
+ } + } + + @* IKE settings summary *@ +
+ IKEv@tunnel.IkeVersion + IKE: @tunnel.IkeProposal + ESP: @tunnel.EspProposal + DPD: @tunnel.DpdDelay s / @tunnel.DpdTimeout s +
+ + @* Actions *@ +
+ @foreach (VpnLocalEndpoint ep in tunnel.LocalEndpoints) + { + + } + @if (!confirmDeleteTunnelId.HasValue || confirmDeleteTunnelId != tunnel.Id) + { + + } + else + { +
+ Delete @tunnel.Name? + + +
+ } +
+ + @* Sync result *@ + @if (syncResultTunnelId == tunnel.Id && syncResult is not null) + { +
+
@syncResult.Output
+
+ } + } +
+
+ } + } +} + +@code { + [Parameter] public Guid TenantId { get; set; } + + private bool loading = true; + private bool saving = false; + private string? errorMessage; + + private List tunnels = []; + private List availableClusters = []; + + // Expand state + private Guid? expandedTunnelId; + private Guid? confirmDeleteTunnelId; + private Guid? confirmDeleteRemoteEndpointId; + + // Sync result display + private Guid? syncResultTunnelId; + private HelmExecutionResult? syncResult; + + // ── Security profiles ──────────────────────────────────────────────────────── + + private record SecurityProfile( + string Key, string Name, string Icon, string Description, bool Recommended, + int IkeVersion, string IkeProposal, string EspProposal); + + private static readonly SecurityProfile[] SecurityProfiles = + [ + new("modern", "Modern", "bi-shield-check", + "AES-256-GCM · SHA-256 · Group 15 (MODP 3072-bit). Best for new deployments — both ends must support IKEv2.", + Recommended: true, + IkeVersion: 2, IkeProposal: "aes256-sha256-modp3072", EspProposal: "aes256gcm16"), + new("compatible", "Compatible", "bi-shield", + "AES-256-CBC · SHA-256 · Group 14 (MODP 2048-bit). Works with most firewalls, hardware VPN routers, and older appliances.", + Recommended: false, + IkeVersion: 2, IkeProposal: "aes256-sha256-modp2048", EspProposal: "aes256-sha256"), + new("highsec", "High Security", "bi-shield-fill-check", + "AES-256-GCM · SHA-384 · Group 20 (ECP P-384). Use when compliance requires NSA Suite B or equivalent.", + Recommended: false, + IkeVersion: 2, IkeProposal: "aes256-sha384-ecp384", EspProposal: "aes256gcm16-sha384"), + ]; + + // ── Add tunnel wizard state ── + private bool showAddForm; + private int wizardStep = 1; + private string newTunnelName = ""; + private string newTunnelDescription = ""; + private VpnTunnelType newTunnelType = VpnTunnelType.SiteToSite; + + // SiteToSite wizard + private string selectedGatewayClusterId = ""; + private string gatewayPodCidr = ""; + private string gatewayServiceCidr = ""; + private bool detectingCidrs; + private bool podCidrDetected; + private bool serviceCidrDetected; + + // ClusterMesh wizard + private Dictionary meshClusterSelections = []; + private Guid? meshBrokerClusterId; + + // Remote endpoint for SiteToSite + private string remoteEndpointName = ""; + private string remotePublicIp = ""; + private string remoteSubnets = ""; + private string remotePsk = ""; + + // Security profile + IKE settings + private string selectedProfile = "modern"; + private int ikeVersion = 2; + private string ikeProposal = "aes256-sha256-modp3072"; + private string espProposal = "aes256gcm16"; + private int dpdDelay = 30; + private int dpdTimeout = 150; + private int ikeLifetime = 86400; + private int childLifetime = 3600; + + // Custom proposal component fields (phase 1) + private string p1Enc = "aes256"; + private string p1Integrity = "sha256"; + private string p1Dh = "modp3072"; + // Custom proposal component fields (phase 2) + private string p2Enc = "aes256gcm16"; + private string p2Integrity = "sha256"; + private string p2Pfs = ""; // empty = no PFS override (inherit from IKE) + + // Inline add-remote-endpoint + private Guid? addRemoteEndpointTunnelId; + private string addRemoteName = ""; + private string addRemoteIp = ""; + private string addRemoteSubnets = ""; + private string addRemotePsk = ""; + private string addRemoteLocalId = ""; + private string addRemoteRemoteId = ""; + + // PSK rotation + private Guid? rotatePskEndpointId; + private string rotatePskValue = ""; + + protected override async Task OnInitializedAsync() + { + await LoadAsync(); + } + + private async Task LoadAsync() + { + loading = true; + errorMessage = null; + + try + { + await VpnService.ReconcileGatewayLinksAsync(TenantId); + tunnels = await VpnService.GetTunnelsForTenantAsync(TenantId); + availableClusters = await TenantService.GetClustersAsync(TenantId); + } + catch (Exception ex) + { + errorMessage = $"Failed to load VPN tunnels: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private async Task OnGatewayClusterChanged(ChangeEventArgs e) + { + selectedGatewayClusterId = e.Value?.ToString() ?? string.Empty; + gatewayPodCidr = ""; + gatewayServiceCidr = ""; + podCidrDetected = false; + serviceCidrDetected = false; + + if (!Guid.TryParse(selectedGatewayClusterId, out Guid clusterId)) return; + + KubernetesCluster? cluster = availableClusters.FirstOrDefault(c => c.Id == clusterId); + if (cluster is null) return; + + detectingCidrs = true; + StateHasChanged(); + + try + { + (string? pod, string? svc) = await VpnService.DetectClusterCidrsAsync(cluster); + if (!string.IsNullOrWhiteSpace(pod)) { gatewayPodCidr = pod; podCidrDetected = true; } + if (!string.IsNullOrWhiteSpace(svc)) { gatewayServiceCidr = svc; serviceCidrDetected = true; } + } + catch { } + finally + { + detectingCidrs = false; + } + } + + private void ApplyProfile(string key) + { + selectedProfile = key; + SecurityProfile? p = SecurityProfiles.FirstOrDefault(x => x.Key == key); + if (p is null) return; + ikeVersion = p.IkeVersion; + ikeProposal = p.IkeProposal; + espProposal = p.EspProposal; + } + + /// + /// Recomputes the ikeProposal and espProposal strings from the custom component + /// dropdowns whenever any of them change. Called via @bind:after on each select. + /// + private void BuildProposals() + { + bool p1IsAead = p1Enc.Contains("gcm") || p1Enc == "chacha20poly1305"; + ikeProposal = p1IsAead + ? $"{p1Enc}-prf{p1Integrity}-{p1Dh}" // AEAD IKE needs explicit PRF + : $"{p1Enc}-{p1Integrity}-{p1Dh}"; + + bool p2IsAead = p2Enc.Contains("gcm") || p2Enc == "chacha20poly1305"; + string pfsPart = p2Pfs == "none" ? "" : (string.IsNullOrEmpty(p2Pfs) ? p1Dh : p2Pfs); + if (p2IsAead) + espProposal = string.IsNullOrEmpty(pfsPart) ? p2Enc : $"{p2Enc}-{pfsPart}"; + else + espProposal = string.IsNullOrEmpty(pfsPart) ? $"{p2Enc}-{p2Integrity}" : $"{p2Enc}-{p2Integrity}-{pfsPart}"; + } + + private void ToggleExpand(Guid tunnelId) + { + expandedTunnelId = expandedTunnelId == tunnelId ? null : tunnelId; + syncResult = null; + syncResultTunnelId = null; + } + + // ── Wizard ────────────────────────────────────────────────────────────────── + + private void WizardNext() + { + errorMessage = null; + + if (wizardStep == 1 && string.IsNullOrWhiteSpace(newTunnelName)) + { + errorMessage = "Please enter a tunnel name."; + return; + } + + if (wizardStep == 2 && newTunnelType == VpnTunnelType.SiteToSite) + { + if (string.IsNullOrWhiteSpace(selectedGatewayClusterId)) + { + errorMessage = "Please select a gateway cluster."; + return; + } + if (string.IsNullOrWhiteSpace(gatewayPodCidr) || string.IsNullOrWhiteSpace(gatewayServiceCidr)) + { + errorMessage = "Please enter both Pod CIDR and Service CIDR."; + return; + } + } + + if (wizardStep == 2 && newTunnelType == VpnTunnelType.ClusterMesh) + { + if (meshClusterSelections.Count(kv => kv.Value.Selected) < 2) + { + errorMessage = "Select at least two clusters for a cluster mesh."; + return; + } + if (!meshBrokerClusterId.HasValue) + { + errorMessage = "Please select which cluster hosts the Submariner broker."; + return; + } + } + + wizardStep++; + } + + private void WizardBack() + { + if (wizardStep > 1) wizardStep--; + errorMessage = null; + } + + private void CancelAdd() + { + showAddForm = false; + wizardStep = 1; + ResetWizardState(); + } + + private void ResetWizardState() + { + newTunnelName = ""; + newTunnelDescription = ""; + newTunnelType = VpnTunnelType.SiteToSite; + selectedGatewayClusterId = ""; + gatewayPodCidr = ""; + gatewayServiceCidr = ""; + meshClusterSelections = []; + meshBrokerClusterId = null; + remoteEndpointName = ""; + remotePublicIp = ""; + remoteSubnets = ""; + remotePsk = ""; + selectedProfile = "modern"; + ikeVersion = 2; + ikeProposal = "aes256-sha256-modp3072"; + espProposal = "aes256gcm16"; + dpdDelay = 30; + dpdTimeout = 150; + detectingCidrs = false; + podCidrDetected = false; + serviceCidrDetected = false; + } + + private async Task CreateTunnel() + { + errorMessage = null; + + if (newTunnelType == VpnTunnelType.SiteToSite && + (string.IsNullOrWhiteSpace(remoteEndpointName) || string.IsNullOrWhiteSpace(remotePublicIp) || + string.IsNullOrWhiteSpace(remoteSubnets) || string.IsNullOrWhiteSpace(remotePsk))) + { + errorMessage = "Please fill in all remote site fields including the PSK."; + return; + } + + saving = true; + try + { + VpnTunnel tunnel = await VpnService.CreateTunnelAsync( + TenantId, newTunnelName, newTunnelType, + string.IsNullOrWhiteSpace(newTunnelDescription) ? null : newTunnelDescription); + + await VpnService.UpdateCryptoSettingsAsync(tunnel.Id, ikeVersion, ikeProposal, espProposal, dpdDelay, dpdTimeout, ikeLifetime, childLifetime); + + if (newTunnelType == VpnTunnelType.SiteToSite) + { + if (Guid.TryParse(selectedGatewayClusterId, out Guid gwClusterId)) + { + string[] localSubnets = [gatewayPodCidr.Trim(), gatewayServiceCidr.Trim()]; + await VpnService.AddLocalEndpointAsync(tunnel.Id, gwClusterId, localSubnets, VpnEndpointRole.Gateway); + } + + string[] subnets = remoteSubnets.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + await VpnService.AddRemoteEndpointAsync( + tunnel.Id, remoteEndpointName.Trim(), remotePublicIp.Trim(), + subnets, VpnAuthMode.Psk, remotePsk, TenantId); + } + else + { + foreach ((Guid clusterId, MeshClusterSelection sel) in meshClusterSelections.Where(kv => kv.Value.Selected)) + { + VpnEndpointRole role = clusterId == meshBrokerClusterId + ? VpnEndpointRole.Gateway + : VpnEndpointRole.Participant; + string[] subnets = [sel.PodCidr.Trim(), sel.ServiceCidr.Trim()]; + await VpnService.AddLocalEndpointAsync(tunnel.Id, clusterId, subnets, role); + } + } + + showAddForm = false; + wizardStep = 1; + ResetWizardState(); + await LoadAsync(); + } + catch (Exception ex) + { + errorMessage = $"Failed to create tunnel: {ex.Message}"; + } + finally + { + saving = false; + } + } + + // ── Mesh cluster selection helpers ─────────────────────────────────────────── + + private void ToggleMeshCluster(Guid clusterId, bool selected) + { + if (!meshClusterSelections.ContainsKey(clusterId)) + meshClusterSelections[clusterId] = new(); + + meshClusterSelections[clusterId].Selected = selected; + + if (!selected && meshBrokerClusterId == clusterId) + meshBrokerClusterId = null; + } + + private void SetMeshClusterCidr(Guid clusterId, string value, bool isPod) + { + if (!meshClusterSelections.ContainsKey(clusterId)) + meshClusterSelections[clusterId] = new(); + + if (isPod) + meshClusterSelections[clusterId].PodCidr = value; + else + meshClusterSelections[clusterId].ServiceCidr = value; + } + + // ── Inline add-remote-endpoint ──────────────────────────────────────────── + + private void StartAddRemoteEndpoint(Guid tunnelId) + { + addRemoteEndpointTunnelId = tunnelId; + addRemoteName = ""; + addRemoteIp = ""; + addRemoteSubnets = ""; + addRemotePsk = ""; + addRemoteLocalId = ""; + addRemoteRemoteId = ""; + } + + private void CancelAddRemoteEndpoint() + { + addRemoteEndpointTunnelId = null; + } + + private void StartRotatePsk(Guid endpointId) + { + rotatePskEndpointId = endpointId; + rotatePskValue = ""; + } + + private async Task RotatePsk(Guid tenantId) + { + if (rotatePskEndpointId is null || string.IsNullOrWhiteSpace(rotatePskValue)) return; + errorMessage = null; + saving = true; + try + { + await VpnService.RotatePskAsync(rotatePskEndpointId.Value, rotatePskValue, tenantId); + rotatePskEndpointId = null; + rotatePskValue = ""; + } + catch (Exception ex) + { + errorMessage = $"Failed to rotate PSK: {ex.Message}"; + } + finally + { + saving = false; + } + } + + private async Task AddRemoteEndpoint(Guid tenantId) + { + if (addRemoteEndpointTunnelId is null) return; + errorMessage = null; + saving = true; + + try + { + string[] subnets = addRemoteSubnets.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + await VpnService.AddRemoteEndpointAsync( + addRemoteEndpointTunnelId.Value, addRemoteName.Trim(), addRemoteIp.Trim(), + subnets, VpnAuthMode.Psk, addRemotePsk, tenantId, + string.IsNullOrWhiteSpace(addRemoteLocalId) ? null : addRemoteLocalId.Trim(), + string.IsNullOrWhiteSpace(addRemoteRemoteId) ? null : addRemoteRemoteId.Trim()); + + addRemoteEndpointTunnelId = null; + await LoadAsync(); + expandedTunnelId = tunnels.FirstOrDefault(t => t.RemoteEndpoints.Any(r => r.Name == addRemoteName))?.Id; + } + catch (Exception ex) + { + errorMessage = $"Failed to add remote site: {ex.Message}"; + } + finally + { + saving = false; + } + } + + private void ConfirmDeleteRemoteEndpoint(Guid endpointId) + { + confirmDeleteRemoteEndpointId = endpointId; + } + + // ── Config sync ────────────────────────────────────────────────────────────── + + private async Task SyncConfig(Guid localEndpointId, Guid tenantId) + { + saving = true; + syncResult = null; + try + { + HelmExecutionResult result = await VpnService.SyncConfigToClusterAsync(localEndpointId, tenantId); + syncResult = result; + syncResultTunnelId = expandedTunnelId; + } + catch (Exception ex) + { + syncResult = new HelmExecutionResult { Success = false, Output = ex.Message }; + syncResultTunnelId = expandedTunnelId; + } + finally + { + saving = false; + } + } + + // ── Delete ──────────────────────────────────────────────────────────────────── + + private async Task DeleteTunnel(Guid tunnelId) + { + saving = true; + errorMessage = null; + try + { + await VpnService.DeleteTunnelAsync(tunnelId); + confirmDeleteTunnelId = null; + if (expandedTunnelId == tunnelId) expandedTunnelId = null; + await LoadAsync(); + } + catch (Exception ex) + { + errorMessage = $"Failed to delete tunnel: {ex.Message}"; + } + finally + { + saving = false; + } + } + + // ── Badge helpers ───────────────────────────────────────────────────────────── + + private static RenderFragment GetTunnelTypeBadge(VpnTunnelType type) => type switch + { + VpnTunnelType.SiteToSite => @Site-to-Site, + VpnTunnelType.ClusterMesh => @Cluster Mesh, + _ => @@type + }; + + private static RenderFragment GetStatusBadge(VpnTunnelStatus status) => status switch + { + VpnTunnelStatus.Active => @Active, + VpnTunnelStatus.Deploying => @Deploying, + VpnTunnelStatus.Degraded => @Degraded, + VpnTunnelStatus.Failed => @Failed, + VpnTunnelStatus.Disabled => @Disabled, + _ => @Draft + }; + + private static RenderFragment GetRoleBadge(VpnEndpointRole role) => role switch + { + VpnEndpointRole.Gateway => @Gateway, + _ => @Participant + }; + + private static RenderFragment GetEndpointStatusBadge(VpnEndpointStatus status) => status switch + { + VpnEndpointStatus.Ready => @Ready, + VpnEndpointStatus.Error => @Error, + _ => @Pending + }; + + private static RenderFragment GetConnectionStatusBadge(VpnConnectionStatus status) => status switch + { + VpnConnectionStatus.Connected => @Connected, + VpnConnectionStatus.Disconnected => @Disconnected, + _ => @Unknown + }; + + // ── Inner types ─────────────────────────────────────────────────────────────── + + private class MeshClusterSelection + { + public bool Selected { get; set; } + public string PodCidr { get; set; } = ""; + public string ServiceCidr { get; set; } = ""; + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/NotificationChannels.razor b/src/EntKube.Web/Components/Pages/Tenants/NotificationChannels.razor new file mode 100644 index 0000000..77aca05 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/NotificationChannels.razor @@ -0,0 +1,354 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.EntityFrameworkCore +@inject IDbContextFactory DbFactory +@inject NotificationService NotificationService + +
+
Notification Channels
+ +
+ +@if (channels.Count == 0) +{ +
+ + No notification channels configured. Add a channel to get alerted via Slack, Teams, email, or webhook. +
+} +else +{ +
+ @foreach (NotificationChannel channel in channels) + { +
+
+
+
+ + @channel.Name + @channel.Type +
+ +
+
+
+ Severity: @FormatSeverityFilter(channel.SeverityFilter) +
+ + @if (deliveries.TryGetValue(channel.Id, out List? recent) && recent.Count > 0) + { +
+ Recent deliveries: + @foreach (NotificationDelivery d in recent.Take(5)) + { + + @(d.Success ? "✓" : "✗") + + } +
+ } + +
+ + +
+ @if (testResults.TryGetValue(channel.Id, out string? result)) + { +
+ @result +
+ } +
+
+
+ } +
+} + +@if (showModal) +{ + +} + +@code { + [Parameter] public required Guid TenantId { get; set; } + + private List channels = []; + private Dictionary> deliveries = new(); + private bool showModal; + private int modalStep = 1; + private NotificationChannelType newType; + private string newName = ""; + private string newWebhookUrl = ""; + private string newEmail = ""; + private string newBearerToken = ""; + private AlertSeverityFilter newSeverityFilter = AlertSeverityFilter.All; + private string? modalError; + private Guid? testingId; + private Dictionary testResults = new(); + + protected override async Task OnInitializedAsync() => await LoadChannels(); + + private async Task LoadChannels() + { + using ApplicationDbContext db = DbFactory.CreateDbContext(); + channels = await db.NotificationChannels + .Where(c => c.TenantId == TenantId) + .OrderBy(c => c.Name) + .ToListAsync(); + + var ids = channels.Select(c => c.Id).ToList(); + List recent = await db.NotificationDeliveries + .Where(d => ids.Contains(d.ChannelId)) + .OrderByDescending(d => d.SentAt) + .ToListAsync(); + + deliveries = recent + .GroupBy(d => d.ChannelId) + .ToDictionary(g => g.Key, g => g.Take(5).ToList()); + } + + private void ShowAddModal() + { + showModal = true; + modalStep = 1; + newName = ""; + newWebhookUrl = ""; + newEmail = ""; + newBearerToken = ""; + newSeverityFilter = AlertSeverityFilter.All; + modalError = null; + } + + private void CloseModal() => showModal = false; + + private void SelectType(NotificationChannelType type) + { + newType = type; + modalStep = 2; + } + + private async Task SaveChannel() + { + modalError = null; + if (string.IsNullOrWhiteSpace(newName)) + { + modalError = "Channel name is required."; + return; + } + + string configJson; + try + { + configJson = BuildConfigJson(); + } + catch (Exception ex) + { + modalError = ex.Message; + return; + } + + using ApplicationDbContext db = DbFactory.CreateDbContext(); + db.NotificationChannels.Add(new NotificationChannel + { + TenantId = TenantId, + Name = newName.Trim(), + Type = newType, + ConfigurationJson = configJson, + SeverityFilter = newSeverityFilter + }); + await db.SaveChangesAsync(); + CloseModal(); + await LoadChannels(); + } + + private string BuildConfigJson() + { + return newType switch + { + NotificationChannelType.Slack or NotificationChannelType.Teams => + !string.IsNullOrWhiteSpace(newWebhookUrl) + ? System.Text.Json.JsonSerializer.Serialize(new { webhookUrl = newWebhookUrl.Trim() }) + : throw new InvalidOperationException("Webhook URL is required."), + + NotificationChannelType.Email => + !string.IsNullOrWhiteSpace(newEmail) + ? System.Text.Json.JsonSerializer.Serialize(new { to = newEmail.Trim() }) + : throw new InvalidOperationException("Email address is required."), + + NotificationChannelType.Webhook => + !string.IsNullOrWhiteSpace(newWebhookUrl) + ? System.Text.Json.JsonSerializer.Serialize(new + { + url = newWebhookUrl.Trim(), + token = string.IsNullOrWhiteSpace(newBearerToken) ? null : newBearerToken.Trim() + }) + : throw new InvalidOperationException("URL is required."), + + _ => "{}" + }; + } + + private async Task ToggleEnabled(NotificationChannel channel) + { + using ApplicationDbContext db = DbFactory.CreateDbContext(); + NotificationChannel? ch = await db.NotificationChannels.FindAsync(channel.Id); + if (ch is null) return; + ch.IsEnabled = !ch.IsEnabled; + await db.SaveChangesAsync(); + channel.IsEnabled = ch.IsEnabled; + } + + private async Task DeleteChannel(NotificationChannel channel) + { + using ApplicationDbContext db = DbFactory.CreateDbContext(); + NotificationChannel? ch = await db.NotificationChannels.FindAsync(channel.Id); + if (ch is not null) db.NotificationChannels.Remove(ch); + await db.SaveChangesAsync(); + await LoadChannels(); + } + + private async Task TestChannel(NotificationChannel channel) + { + testingId = channel.Id; + testResults.Remove(channel.Id); + StateHasChanged(); + + try + { + await NotificationService.SendTestAsync(channel); + testResults[channel.Id] = "✓ Test notification sent successfully"; + } + catch (Exception ex) + { + testResults[channel.Id] = $"✗ {ex.Message}"; + } + finally + { + testingId = null; + StateHasChanged(); + } + } + + private static string TypeIcon(NotificationChannelType type) => type switch + { + NotificationChannelType.Slack => "bi bi-slack", + NotificationChannelType.Teams => "bi bi-microsoft-teams", + NotificationChannelType.Email => "bi bi-envelope-fill", + NotificationChannelType.Webhook => "bi bi-link-45deg", + _ => "bi bi-bell" + }; + + private static string TypeColor(NotificationChannelType type) => type switch + { + NotificationChannelType.Slack => "success", + NotificationChannelType.Teams => "primary", + NotificationChannelType.Email => "warning", + NotificationChannelType.Webhook => "secondary", + _ => "secondary" + }; + + private static string FormatSeverityFilter(AlertSeverityFilter filter) => filter switch + { + AlertSeverityFilter.All => "All", + AlertSeverityFilter.WarningAndAbove => "Warning+", + AlertSeverityFilter.CriticalOnly => "Critical", + _ => filter.ToString() + }; +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/RabbitMQMonitoringPanel.razor b/src/EntKube.Web/Components/Pages/Tenants/RabbitMQMonitoringPanel.razor new file mode 100644 index 0000000..c6bdefd --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/RabbitMQMonitoringPanel.razor @@ -0,0 +1,102 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.EntityFrameworkCore +@inject PrometheusService PrometheusService +@inject IDbContextFactory DbFactory + +@if (isLoading) +{ +
+ Loading RabbitMQ metrics… +
+} +else if (summaries.Count == 0) +{ +
+ + No RabbitMQ clusters found, or Prometheus metrics are unavailable for this cluster. +
+} +else +{ + @foreach (RabbitMQMetricsSummary summary in summaries) + { + bool hasBacklog = summary.UnackedMessages > 0 || summary.ReadyMessages > 100; +
+
+ + @summary.ClusterName + @if (hasBacklog) + { + Backlog + } + @summary.QueriedAt.ToLocalTime().ToString("HH:mm:ss") +
+
+
+
+
Connections
+
@summary.Connections
+
+
+
Channels
+
@summary.Channels
+
+
+
Ready Messages
+
@summary.ReadyMessages.ToString("N0")
+
+
+
Unacknowledged
+
@summary.UnackedMessages.ToString("N0")
+
+
+
Publish rate
+
@summary.PublishRatePerSec.ToString("F1")/s
+
+
+
Deliver rate
+
@summary.DeliverRatePerSec.ToString("F1")/s
+
+
+
+
+ } +} + +@code { + [Parameter] public required Guid ClusterId { get; set; } + + private List summaries = []; + private bool isLoading = true; + + protected override async Task OnParametersSetAsync() => await LoadMetrics(); + + private async Task LoadMetrics() + { + isLoading = true; + StateHasChanged(); + + List rabbitNames; + using (ApplicationDbContext db = DbFactory.CreateDbContext()) + { + rabbitNames = await db.RabbitMQClusters + .Where(c => c.KubernetesClusterId == ClusterId) + .Select(c => c.Name) + .ToListAsync(); + } + + List results = []; + foreach (string name in rabbitNames) + { + KubernetesOperationResult result = + await PrometheusService.GetRabbitMQMetricsAsync(ClusterId, name); + + if (result.IsSuccess && result.Data is not null) + results.Add(result.Data); + } + + summaries = results; + isLoading = false; + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/RegistryTab.razor b/src/EntKube.Web/Components/Pages/Tenants/RegistryTab.razor new file mode 100644 index 0000000..dbf55a0 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/RegistryTab.razor @@ -0,0 +1,1062 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject HarborService HarborService +@inject TenantService TenantService + +@if (loading) +{ +
+
+

Scanning for Harbor instances...

+
+} +else if (selectedProject is not null && selectedHarbor is not null) +{ + @* ── Level 3: Project repositories ── *@ + + +
+ +
+
@selectedProject.Name
+ + @if (selectedProject.Public) { Public } + else { Private } + @selectedProject.RepoCount repos · @FormatSize(selectedProject.StorageUsedBytes) used + +
+
+ + @if (loadingRepos) + { +
+ } + else if (repos is not null && repos.Count == 0) + { +
+ No repositories in this project yet. + Push an image to @(selectedHarbor.Config?.RegistryUrl?.TrimEnd('/') ?? "registry")/@selectedProject.Name/your-image:tag to get started. +
+ } + else if (repos is not null) + { +
+ @foreach (HarborRepositoryInfo repo in repos) + { + string shortName = repo.Name.Contains('/') ? repo.Name[(repo.Name.IndexOf('/') + 1)..] : repo.Name; + bool isExpanded = expandedRepo == repo.Name; + +
+ + + @if (isExpanded) + { +
+ @if (loadingArtifacts) + { +
+ } + else if (artifacts is not null && artifacts.Count == 0) + { +

No artifacts.

+ } + else if (artifacts is not null) + { + + + + + + + + + + + @foreach (HarborArtifactInfo artifact in artifacts) + { + + + + + + + } + +
TagsDigestSizePushed
+ @if (artifact.Tags.Count == 0) + { + untagged + } + else + { + @foreach (string tag in artifact.Tags) + { + @tag + } + } + @artifact.Digest[..19]…@FormatSize(artifact.SizeBytes)@artifact.PushedAt.ToString("yyyy-MM-dd HH:mm")
+ } +
+ } +
+ } +
+ } + @if (repoError is not null) + { +
@repoError
+ } +} +else if (selectedHarbor is not null) +{ + @* ── Level 2: Harbor instance detail ── *@ + + +
+
+ +
+
@selectedHarbor.DisplayName
+ + @if (selectedHarbor.ClusterDisplayName is not null) + { + @selectedHarbor.ClusterDisplayName + } + @if (selectedHarbor.Config?.RegistryUrl is not null) + { + @selectedHarbor.Config.RegistryUrl + } + +
+
+ + @if (!selectedHarbor.IsConfigured) + { + Not configured + } + else + { + + Configured + + } +
+ + @if (!selectedHarbor.IsConfigured) + { +
+ + This Harbor instance has no admin credentials configured. Configure it from the + Components tab on the cluster detail page. +
+ } + else + { + + + @* ── Projects tab ── *@ + @if (instanceTab == "projects") + { +
+ @(projects?.Count ?? 0) projects + +
+ + @if (showCreateProject) + { +
+
+ @if (projectError is not null) + { +
@projectError
+ } +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ } + + @if (loadingProjects) + { +
+ } + else if (projectError is not null && !showCreateProject) + { +
@projectError
+ } + else if (projects is not null) + { +
+ @foreach (HarborProjectInfo project in projects) + { + HarborProject? tracked = trackedProjects?.FirstOrDefault(p => p.ProjectName == project.Name); + bool showingPortalForm = portalLinkProjectName == project.Name; + +
+
+ + +
+ @if (confirmDeleteProject == project.Name) + { + Delete? + + + } + else + { + @if (tracked?.LinkedApp is not null) + { + + } + else + { + + } + + } +
+
+ + @if (showingPortalForm) + { +
+ @if (portalLinkError is not null) + { +
@portalLinkError
+ } +
+ + + +
+
+ The customer will see a Registry button in their portal for this app and can browse repositories and manage robot accounts. +
+
+ } +
+ } +
+ } + } + + @* ── Robot Accounts tab ── *@ + @if (instanceTab == "robots") + { +
+ @(robots?.Count ?? 0) robot accounts + +
+ + @if (showCreateRobot) + { +
+
+ @if (robotError is not null) + { +
@robotError
+ } + @if (newRobotSecret is not null) + { +
+ Robot created! Copy the secret now — it will not be shown again. +
@newRobotSecret
+
The secret is also stored in Vault as harbor-robot-@newRobotName.
+ +
+ } + else + { +
+
+ + +
+
+ + +
+
+ + +
+ @if (newRobotScope == "project") + { +
+ + +
+ } +
+
+ + +
+
+ + +
+
+
+
+ + +
+ } +
+
+ } + + @if (loadingRobots) + { +
+ } + else if (robotListError is not null) + { +
@robotListError
+ } + else if (robots is not null) + { + @if (robots.Count == 0) + { +

No robot accounts yet.

+ } + else + { +
+ @foreach (HarborRobotInfo robot in robots) + { +
+
+
+
+ + @robot.Name + @if (robot.Disabled) + { + Disabled + } + else + { + Active + } +
+ @if (!string.IsNullOrWhiteSpace(robot.Description)) + { +
@robot.Description
+ } +
+ Created @robot.CreatedAt.ToString("yyyy-MM-dd") + @if (robot.ExpiresAt == -1) + { + · Never expires + } + else + { + DateTime expiry = DateTimeOffset.FromUnixTimeSeconds(robot.ExpiresAt).UtcDateTime; + · Expires @expiry.ToString("yyyy-MM-dd") + } +
+
+ + @if (confirmDeleteRobot == robot.Id) + { +
+ Delete? + + +
+ } + else + { + + } +
+
+ } +
+ } + } + } + + @* ── System Info tab ── *@ + @if (instanceTab == "info") + { + @if (loadingInfo) + { +
+ } + else if (infoError is not null) + { +
@infoError
+ } + else if (systemInfo is not null) + { +
+
+
+
Harbor Version
+
@(systemInfo.HarborVersion ?? "—")
+ +
Registry URL
+
+ @if (systemInfo.RegistryUrl is not null) + { + @systemInfo.RegistryUrl + } + else + { + + } +
+ +
Auth Mode
+
@(systemInfo.AuthMode ?? "—")
+ +
Trivy Scanner
+
+ @if (systemInfo.WithTrivy) + { + Enabled + } + else + { + Disabled + } +
+
+
+
+ + @if (selectedHarbor.Config?.RegistryUrl is not null) + { +
+
Quick start
+
+
# Login
+
docker login @selectedHarbor.Config.RegistryUrl.TrimEnd('/')
+
# Pull an image
+
docker pull @selectedHarbor.Config.RegistryUrl.TrimEnd('/')/project/image:tag
+
# Add as Helm repo
+
helm repo add my-harbor oci://@selectedHarbor.Config.RegistryUrl.TrimEnd('/')/project
+
+
+ } + } + } + } +} +else +{ + @* ── Level 1: List of Harbor instances ── *@ + @if (harborInstances is null || harborInstances.Count == 0) + { +
+ + No Harbor instances found. Install Harbor from the Components tab on a cluster. +
+ } + else + { +
+ @foreach (DetectedHarbor harbor in harborInstances) + { +
+
+
+
+
+ +
+
@harbor.DisplayName
+ @if (harbor.ClusterDisplayName is not null) + { +
+ @harbor.ClusterDisplayName +
+ } +
+
+ @if (harbor.IsConfigured) + { + + Ready + + } + else + { + + Not configured + + } +
+ @if (harbor.Config?.RegistryUrl is not null) + { +
@harbor.Config.RegistryUrl
+ } +
+ +
+
+ } +
+ } +} + +@code { + [Parameter] public Guid TenantId { get; set; } + + private bool loading = true; + private List? harborInstances; + private DetectedHarbor? selectedHarbor; + private string instanceTab = "projects"; + + // Projects + private List? projects; + private bool loadingProjects; + private bool showCreateProject; + private bool savingProject; + private string newProjectName = ""; + private string newProjectDescription = ""; + private bool newProjectPublic; + private string? projectError; + private string? confirmDeleteProject; + + // Repositories (level 3) + private HarborProjectInfo? selectedProject; + private List? repos; + private bool loadingRepos; + private string? expandedRepo; + private List? artifacts; + private bool loadingArtifacts; + private string? repoError; + + // Robots + private List? robots; + private bool loadingRobots; + private bool showCreateRobot; + private bool savingRobot; + private string newRobotName = ""; + private string newRobotDescription = ""; + private string newRobotScope = "system"; + private string newRobotProject = ""; + private bool newRobotCanPull = true; + private bool newRobotCanPush; + private string? newRobotSecret; + private string? robotError; + private string? robotListError; + private long? confirmDeleteRobot; + + // Info + private HarborSystemInfo? systemInfo; + private bool loadingInfo; + private string? infoError; + + // Portal linking + private List? trackedProjects; + private List? allApps; + private string? portalLinkProjectName; + private Guid portalLinkAppId = Guid.Empty; + private bool savingPortalLink; + private string? portalLinkError; + + protected override async Task OnInitializedAsync() + { + harborInstances = await HarborService.GetDetectedHarborInstancesAsync(TenantId); + loading = false; + } + + private async Task SelectHarbor(DetectedHarbor harbor) + { + selectedHarbor = harbor; + instanceTab = "projects"; + selectedProject = null; + trackedProjects = null; + portalLinkProjectName = null; + + if (harbor.IsConfigured && harbor.Config is not null) + { + // Load tracked projects and available apps in parallel. + Task> trackedTask = HarborService.GetTrackedProjectsAsync(TenantId, harbor.Config.Id); + Task> appsTask = TenantService.GetAppsWithCustomersAsync(TenantId); + await Task.WhenAll(trackedTask, appsTask); + trackedProjects = trackedTask.Result; + allApps = appsTask.Result; + + await SwitchTab("projects"); + } + } + + private void BackToList() + { + selectedHarbor = null; + selectedProject = null; + projects = null; + robots = null; + systemInfo = null; + trackedProjects = null; + allApps = null; + portalLinkProjectName = null; + } + + private void BackToProjects() + { + selectedProject = null; + repos = null; + artifacts = null; + expandedRepo = null; + } + + private async Task SwitchTab(string tab) + { + instanceTab = tab; + projectError = null; + robotError = null; + robotListError = null; + infoError = null; + + if (tab == "projects") + { + await LoadProjects(); + } + else if (tab == "robots") + { + await LoadRobots(); + } + else if (tab == "info") + { + await LoadInfo(); + } + } + + private async Task LoadProjects() + { + if (selectedHarbor?.Config is null) return; + + loadingProjects = true; + projectError = null; + StateHasChanged(); + + try + { + projects = await HarborService.GetProjectsAsync(TenantId, selectedHarbor.Config); + } + catch (Exception ex) + { + projectError = ex.Message; + } + finally + { + loadingProjects = false; + } + } + + private async Task LoadRobots() + { + if (selectedHarbor?.Config is null) return; + + loadingRobots = true; + robotListError = null; + StateHasChanged(); + + try + { + robots = await HarborService.GetRobotsAsync(TenantId, selectedHarbor.Config); + } + catch (Exception ex) + { + robotListError = ex.Message; + } + finally + { + loadingRobots = false; + } + } + + private async Task LoadInfo() + { + if (selectedHarbor?.Config is null) return; + + loadingInfo = true; + infoError = null; + StateHasChanged(); + + try + { + systemInfo = await HarborService.GetSystemInfoAsync(TenantId, selectedHarbor.Config); + } + catch (Exception ex) + { + infoError = ex.Message; + } + finally + { + loadingInfo = false; + } + } + + private async Task OpenProject(HarborProjectInfo project) + { + if (selectedHarbor?.Config is null) return; + + selectedProject = project; + repos = null; + artifacts = null; + expandedRepo = null; + repoError = null; + loadingRepos = true; + StateHasChanged(); + + try + { + repos = await HarborService.GetRepositoriesAsync(TenantId, selectedHarbor.Config, project.Name); + } + catch (Exception ex) + { + repoError = ex.Message; + } + finally + { + loadingRepos = false; + } + } + + private async Task ToggleRepo(HarborRepositoryInfo repo) + { + if (selectedHarbor?.Config is null || selectedProject is null) return; + + if (expandedRepo == repo.Name) + { + expandedRepo = null; + artifacts = null; + return; + } + + expandedRepo = repo.Name; + artifacts = null; + loadingArtifacts = true; + repoError = null; + StateHasChanged(); + + try + { + string shortName = repo.Name.Contains('/') ? repo.Name[(repo.Name.IndexOf('/') + 1)..] : repo.Name; + artifacts = await HarborService.GetArtifactsAsync( + TenantId, selectedHarbor.Config, selectedProject.Name, shortName); + } + catch (Exception ex) + { + repoError = ex.Message; + } + finally + { + loadingArtifacts = false; + } + } + + private async Task CreateProject() + { + if (selectedHarbor?.Config is null || string.IsNullOrWhiteSpace(newProjectName)) return; + + savingProject = true; + projectError = null; + + try + { + await HarborService.CreateProjectAsync( + TenantId, selectedHarbor.Config, + newProjectName.Trim(), newProjectPublic, + string.IsNullOrWhiteSpace(newProjectDescription) ? null : newProjectDescription.Trim()); + + projects = await HarborService.GetProjectsAsync(TenantId, selectedHarbor.Config); + showCreateProject = false; + newProjectName = ""; + newProjectDescription = ""; + newProjectPublic = false; + } + catch (Exception ex) + { + projectError = ex.Message; + } + finally + { + savingProject = false; + } + } + + private async Task DeleteProject(string projectName) + { + if (selectedHarbor?.Config is null) return; + + confirmDeleteProject = null; + projectError = null; + + try + { + await HarborService.DeleteProjectAsync(TenantId, selectedHarbor.Config, projectName); + projects = await HarborService.GetProjectsAsync(TenantId, selectedHarbor.Config); + } + catch (Exception ex) + { + projectError = ex.Message; + } + } + + private async Task CreateRobot() + { + if (selectedHarbor?.Config is null || string.IsNullOrWhiteSpace(newRobotName)) return; + + savingRobot = true; + robotError = null; + + try + { + IEnumerable? projectNames = newRobotScope == "project" + ? (!string.IsNullOrWhiteSpace(newRobotProject) ? [newRobotProject] : null) + : null; + + string secret = await HarborService.CreateRobotAsync( + TenantId, selectedHarbor.Config, + newRobotName.Trim(), + string.IsNullOrWhiteSpace(newRobotDescription) ? null : newRobotDescription.Trim(), + projectNames, + newRobotCanPush, newRobotCanPull); + + newRobotSecret = secret; + robots = await HarborService.GetRobotsAsync(TenantId, selectedHarbor.Config); + newRobotDescription = ""; + newRobotScope = "system"; + newRobotProject = ""; + newRobotCanPull = true; + newRobotCanPush = false; + } + catch (Exception ex) + { + robotError = ex.Message; + } + finally + { + savingRobot = false; + } + } + + private async Task DeleteRobot(long robotId) + { + if (selectedHarbor?.Config is null) return; + + confirmDeleteRobot = null; + robotListError = null; + + try + { + await HarborService.DeleteRobotAsync(TenantId, selectedHarbor.Config, robotId); + robots = await HarborService.GetRobotsAsync(TenantId, selectedHarbor.Config); + } + catch (Exception ex) + { + robotListError = ex.Message; + } + } + + // ── Portal linking ──────────────────────────────────────────────────────── + + private void ShowPortalLinkForm(string projectName) + { + portalLinkProjectName = projectName; + portalLinkAppId = Guid.Empty; + portalLinkError = null; + } + + private async Task LinkProjectToApp(string projectName) + { + if (selectedHarbor?.Config is null || portalLinkAppId == Guid.Empty) return; + + savingPortalLink = true; + portalLinkError = null; + + try + { + await HarborService.LinkProjectToAppAsync( + TenantId, selectedHarbor.Config.Id, projectName, portalLinkAppId); + + trackedProjects = await HarborService.GetTrackedProjectsAsync(TenantId, selectedHarbor.Config.Id); + portalLinkProjectName = null; + } + catch (Exception ex) + { + portalLinkError = ex.Message; + } + finally + { + savingPortalLink = false; + } + } + + private async Task UnlinkProject(Guid projectId) + { + if (selectedHarbor?.Config is null) return; + + try + { + await HarborService.UnlinkProjectFromAppAsync(TenantId, projectId); + trackedProjects = await HarborService.GetTrackedProjectsAsync(TenantId, selectedHarbor.Config.Id); + } + catch (Exception ex) + { + projectError = ex.Message; + } + } + + private static string FormatSize(long bytes) + { + if (bytes <= 0) return "0 B"; + if (bytes < 1024) return $"{bytes} B"; + if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; + return $"{bytes / (1024.0 * 1024 * 1024):F1} GB"; + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/ScrapeTargetsPanel.razor b/src/EntKube.Web/Components/Pages/Tenants/ScrapeTargetsPanel.razor new file mode 100644 index 0000000..ad38cb9 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/ScrapeTargetsPanel.razor @@ -0,0 +1,155 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@inject PrometheusService PrometheusService + +
+ + + @filtered.Count of @targets.Count targets + +
+ +@if (isLoading) +{ +
+ Loading scrape targets… +
+} +else if (errorMessage is not null) +{ +
+ @errorMessage +
+} +else +{ + @if (DownCount > 0) + { +
+ + @DownCount target@(DownCount == 1 ? "" : "s") down — check the affected exporters. +
+ } + + @foreach (IGrouping group in filtered.GroupBy(t => t.Pool).OrderBy(g => g.Key)) + { + bool anyDown = group.Any(t => t.Health == "down"); +
+
+ @group.Key + @group.Count() +
+
+ + + @foreach (ScrapeTarget t in group.OrderBy(t => t.Health == "up" ? 1 : 0)) + { + + + + + + + } + +
+ @if (t.Health == "up") + { + UP + } + else + { + DOWN + } + + @t.Labels.GetValueOrDefault("instance", t.ScrapeUrl) + + @if (t.Labels.TryGetValue("namespace", out string? ns)) + { + @ns + } + @if (t.Labels.TryGetValue("job", out string? job)) + { + @job + } + + @if (!string.IsNullOrEmpty(t.LastError)) + { + + Error + + } + else if (t.LastScrape != default) + { + + @t.LastScrapeDurationSeconds.ToString("F2")s ago + + } +
+
+
+ } +} + +@code { + [Parameter] public required Guid ClusterId { get; set; } + + private List targets = []; + private List filtered = []; + private string filterHealth = ""; + private string filterText = ""; + private bool isLoading = true; + private string? errorMessage; + + protected override async Task OnParametersSetAsync() => await Load(); + + private async Task Load() + { + isLoading = true; + errorMessage = null; + StateHasChanged(); + + KubernetesOperationResult> result = + await PrometheusService.GetScrapeTargetsAsync(ClusterId); + + if (result.IsSuccess) + { + targets = result.Data ?? []; + ApplyFilter(); + } + else + { + errorMessage = result.Error; + targets = []; + filtered = []; + } + isLoading = false; + } + + private void OnSearchInput(ChangeEventArgs e) + { + filterText = e.Value?.ToString() ?? string.Empty; + ApplyFilter(); + } + + private int DownCount => targets.Count(t => t.Health == "down"); + + private void ApplyFilter() + { + filtered = targets.Where(t => + (string.IsNullOrEmpty(filterHealth) || t.Health == filterHealth) && + (string.IsNullOrEmpty(filterText) || + t.Pool.Contains(filterText, StringComparison.OrdinalIgnoreCase) || + t.ScrapeUrl.Contains(filterText, StringComparison.OrdinalIgnoreCase) || + t.Labels.Values.Any(v => v.Contains(filterText, StringComparison.OrdinalIgnoreCase))) + ).ToList(); + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/SlaTargetManager.razor b/src/EntKube.Web/Components/Pages/Tenants/SlaTargetManager.razor new file mode 100644 index 0000000..7eba6ce --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/SlaTargetManager.razor @@ -0,0 +1,251 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.EntityFrameworkCore +@inject IncidentService IncidentService +@inject IDbContextFactory DbFactory + +
+
SLA Targets
+ +
+ +
+ Define uptime targets per customer or app. More specific targets (app > customer) take precedence. +
+ +@if (targets.Count == 0) +{ +
+ + No SLA targets configured. Add one to track uptime compliance. +
+} +else +{ +
+ + + + + + + + + + + + @foreach (SlaTargetRow row in targets) + { + + + + + + + + } + +
ScopeTargetWindowCompliance (30d)
+ @if (row.AppName is not null) + { + @row.AppName +
@row.CustomerName
+ } + else if (row.CustomerName is not null) + { + @row.CustomerName + } + else + { + All customers (tenant default) + } +
@row.Target.TargetPercent.ToString("F1")%@row.Target.MeasurementWindowDays days + @if (row.CompliancePercent.HasValue) + { + bool met = row.CompliancePercent.Value >= row.Target.TargetPercent; + + + @row.CompliancePercent.Value.ToString("F2")% + + } + else + { + No data + } + + +
+
+} + +@if (showAdd) +{ +
+
+
Add SLA Target
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ @if (!string.IsNullOrEmpty(formError)) + { +
@formError
+ } +
+ + +
+
+
+} + +@code { + [Parameter] public required Guid TenantId { get; set; } + + private List targets = []; + private List customers = []; + private List filteredApps = []; + private bool showAdd; + private string newCustomerId = ""; + private string newAppId = ""; + private double newTargetPercent = 99.9; + private int newWindowDays = 30; + private string? formError; + + protected override async Task OnInitializedAsync() => await Load(); + + private async Task Load() + { + using ApplicationDbContext db = DbFactory.CreateDbContext(); + + customers = await db.Customers + .Include(c => c.Apps) + .Where(c => c.TenantId == TenantId) + .OrderBy(c => c.Name) + .ToListAsync(); + + List slaTargets = await db.SlaTargets + .Include(s => s.Customer) + .Include(s => s.App) + .Where(s => s.TenantId == TenantId) + .OrderBy(s => s.Customer!.Name).ThenBy(s => s.App!.Name) + .ToListAsync(); + + // Compute compliance for each target + targets = []; + foreach (SlaTarget t in slaTargets) + { + // Find all deployments in scope + List deploymentIds = await db.AppDeployments + .Where(d => (t.AppId == null || d.AppId == t.AppId) + && (t.CustomerId == null || d.App.CustomerId == t.CustomerId) + && d.App.Customer.TenantId == TenantId) + .Select(d => d.Id) + .ToListAsync(); + + double? compliance = null; + if (deploymentIds.Count > 0) + { + DateTime from = DateTime.UtcNow.AddDays(-t.MeasurementWindowDays); + var snaps = await db.DeploymentHealthSnapshots + .Where(s => deploymentIds.Contains(s.DeploymentId) && s.SnapshotAt >= from) + .Select(s => s.HealthStatus) + .ToListAsync(); + + if (snaps.Count > 0) + compliance = Math.Round((double)snaps.Count(s => s == HealthStatus.Healthy) / snaps.Count * 100, 2); + } + + targets.Add(new SlaTargetRow(t, t.Customer?.Name, t.App?.Name, compliance)); + } + } + + private async Task OnCustomerChanged() + { + filteredApps = []; + newAppId = ""; + if (!Guid.TryParse(newCustomerId, out Guid cid)) return; + Customer? customer = customers.FirstOrDefault(c => c.Id == cid); + if (customer is not null) + filteredApps = customer.Apps.OfType().OrderBy(a => a.Name).ToList(); + } + + private void ShowAdd() + { + showAdd = true; + newCustomerId = ""; + newAppId = ""; + newTargetPercent = 99.9; + newWindowDays = 30; + filteredApps = []; + formError = null; + } + + private async Task Save() + { + formError = null; + if (newTargetPercent < 50 || newTargetPercent > 100) + { + formError = "Target must be between 50% and 100%."; + return; + } + + Guid? customerId = Guid.TryParse(newCustomerId, out Guid cid) ? cid : null; + Guid? appId = Guid.TryParse(newAppId, out Guid aid) ? aid : null; + + await IncidentService.UpsertSlaTargetAsync( + TenantId, customerId, appId, newTargetPercent, newWindowDays); + + showAdd = false; + await Load(); + } + + private async Task Delete(SlaTarget target) + { + using ApplicationDbContext db = DbFactory.CreateDbContext(); + SlaTarget? found = await db.SlaTargets.FindAsync(target.Id); + if (found is not null) db.SlaTargets.Remove(found); + await db.SaveChangesAsync(); + await Load(); + } + + private record SlaTargetRow(SlaTarget Target, string? CustomerName, string? AppName, double? CompliancePercent); +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor b/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor new file mode 100644 index 0000000..de1ee16 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor @@ -0,0 +1,632 @@ +@using System.IO +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.AspNetCore.Components.Forms +@inject StorageBrowserService BrowserService +@inject IJSRuntime JS +@implements IAsyncDisposable + +
+
+
+ Browse: + @Link.BucketName + @GetProviderLabel() +
+ +
+
+ + @if (!string.IsNullOrEmpty(error)) + { +
+ @error +
+ } + + @* Breadcrumb + toolbar *@ +
+ +
+ @if (!showNewFolderInput) + { + + } + +
+
+ + @* New folder input *@ + @if (showNewFolderInput) + { +
+ + + +
+ } + + @if (uploadingFile) + { +
+ Uploading... +
+ } + + @if (loading) + { +
+ Loading... +
+ } + else if (listing is not null) + { +
+ + + + + + + + + + + + @foreach (string folder in listing.Folders) + { + string folderName = folder.TrimEnd('/').Split('/').Last(); + bool folderSelected = selectedKeys.Contains(folder); + + + + + + + + } + @foreach (StorageObject obj in listing.Objects) + { + string fileName = obj.Name; + string fileKey = obj.Key; + bool fileSelected = selectedKeys.Contains(fileKey); + + + + + + + + } + @if (listing.Folders.Count == 0 && listing.Objects.Count == 0) + { + + + + } + +
+ + NameSizeModified
+ + + + + +
+ + + + @fileName + @FormatSize(obj.SizeBytes)@obj.LastModified.ToString("yyyy-MM-dd HH:mm") + + + +
+ This folder is empty +
+
+ @if (selectedKeys.Count > 0) + { +
+ +
+ } + @if (sharedUrl is not null) + { +
+
+ +
+
+ Presigned link for @sharedUrlFileName + — valid for 1 hour +
+
+ + +
+
+ +
+
+ } + } +
+
+ +@code { + [Parameter] public Guid TenantId { get; set; } + [Parameter] public StorageLink Link { get; set; } = null!; + [Parameter] public EventCallback OnClose { get; set; } + + private string currentPrefix = ""; + private List breadcrumbs = new(); + private StorageBrowseResult? listing; + private bool loading = true; + private string? error; + private bool uploadingFile; + private bool creatingFolder; + private string newFolderName = ""; + private bool showNewFolderInput; + private HashSet selectedKeys = new(); + private bool deleting; + private string? downloadingKey; + private string? generatingLinkKey; + private string? sharedUrl; + private string? sharedUrlFileName; + private bool linkCopied; + private IJSObjectReference? jsModule; + + private bool AllSelected => + listing is not null && + (listing.Folders.Count + listing.Objects.Count) > 0 && + listing.Folders.All(f => selectedKeys.Contains(f)) && + listing.Objects.All(o => selectedKeys.Contains(o.Key)); + + protected override async Task OnInitializedAsync() + { + await LoadListing(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + jsModule = await JS.InvokeAsync( + "import", "./Components/Pages/Tenants/StorageBrowser.razor.js"); + } + } + + public async ValueTask DisposeAsync() + { + if (jsModule is not null) + await jsModule.DisposeAsync(); + } + + private void UpdateBreadcrumbs() + { + breadcrumbs.Clear(); + string[] parts = currentPrefix.TrimEnd('/').Split('/', StringSplitOptions.RemoveEmptyEntries); + string built = ""; + for (int i = 0; i < parts.Length; i++) + { + string part = parts[i]; + built += part + "/"; + breadcrumbs.Add(new BreadcrumbItem { Label = part, Path = built, IsLast = i == parts.Length - 1 }); + } + } + + private async Task LoadListing() + { + loading = true; + error = null; + selectedKeys.Clear(); + UpdateBreadcrumbs(); + StateHasChanged(); + try + { + listing = await BrowserService.ListAsync(TenantId, Link, currentPrefix); + } + catch (Exception ex) + { + error = ex.Message; + listing = null; + } + finally + { + loading = false; + } + } + + private async Task NavigateToRoot() + { + currentPrefix = ""; + await LoadListing(); + } + + private async Task NavigateTo(string prefix) + { + currentPrefix = prefix; + await LoadListing(); + } + + private void ShowNewFolderInput() + { + showNewFolderInput = true; + } + + private async Task HandleFileUpload(InputFileChangeEventArgs e) + { + uploadingFile = true; + error = null; + StateHasChanged(); + try + { + foreach (IBrowserFile file in e.GetMultipleFiles(20)) + { + string key = currentPrefix + file.Name; + using Stream stream = file.OpenReadStream(maxAllowedSize: 100 * 1024 * 1024); + await BrowserService.UploadAsync(TenantId, Link, key, stream, file.ContentType); + } + await LoadListing(); + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + uploadingFile = false; + } + } + + private async Task CreateFolder() + { + if (string.IsNullOrWhiteSpace(newFolderName)) return; + creatingFolder = true; + error = null; + try + { + string folderPath = currentPrefix + newFolderName.Trim('/'); + await BrowserService.CreateFolderAsync(TenantId, Link, folderPath); + newFolderName = ""; + showNewFolderInput = false; + await LoadListing(); + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + creatingFolder = false; + } + } + + private void CancelNewFolder() + { + showNewFolderInput = false; + newFolderName = ""; + } + + private async Task OnFolderNameKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Enter") await CreateFolder(); + else if (e.Key == "Escape") CancelNewFolder(); + } + + private async Task DownloadFile(string key, string fileName) + { + downloadingKey = key; + error = null; + StateHasChanged(); + try + { + byte[] bytes = await BrowserService.DownloadAsync(TenantId, Link, key); + if (jsModule is not null) + await jsModule.InvokeVoidAsync("downloadFile", fileName, GetContentType(fileName), bytes); + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + downloadingKey = null; + } + } + + private async Task GenerateLink(string key, string fileName) + { + generatingLinkKey = key; + sharedUrl = null; + linkCopied = false; + error = null; + StateHasChanged(); + try + { + sharedUrl = await BrowserService.GetPresignedUrlAsync(TenantId, Link, key); + sharedUrlFileName = fileName; + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + generatingLinkKey = null; + } + } + + private async Task CopyLink() + { + if (sharedUrl is null || jsModule is null) return; + await jsModule.InvokeVoidAsync("copyToClipboard", sharedUrl); + linkCopied = true; + StateHasChanged(); + await Task.Delay(2000); + linkCopied = false; + } + +private async Task DeleteObject(string key) + { + error = null; + try + { + await BrowserService.DeleteAsync(TenantId, Link, key); + await LoadListing(); + } + catch (Exception ex) + { + error = ex.Message; + } + } + + private async Task DeleteFolder(string prefix) + { + error = null; + try + { + await BrowserService.DeleteFolderAsync(TenantId, Link, prefix); + await LoadListing(); + } + catch (Exception ex) + { + error = ex.Message; + } + } + + private async Task DeleteSelected() + { + if (selectedKeys.Count == 0) return; + deleting = true; + error = null; + try + { + List folders = new(selectedKeys.Where(k => k.EndsWith('/'))); + List files = new(selectedKeys.Where(k => !k.EndsWith('/'))); + + foreach (string folder in folders) + await BrowserService.DeleteFolderAsync(TenantId, Link, folder); + + if (files.Count > 0) + await BrowserService.DeleteManyAsync(TenantId, Link, files); + + selectedKeys.Clear(); + await LoadListing(); + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + deleting = false; + } + } + + private void ToggleKey(string key, bool selected) + { + if (selected) selectedKeys.Add(key); + else selectedKeys.Remove(key); + } + + private void ToggleSelectAll(ChangeEventArgs e) + { + if (listing is null) return; + if ((bool)e.Value!) + { + selectedKeys.UnionWith(listing.Folders); + selectedKeys.UnionWith(listing.Objects.Select(o => o.Key)); + } + else + { + selectedKeys.Clear(); + } + } + + private string GetProviderLabel() + { + return Link.Provider switch + { + StorageProvider.AwsS3 => "AWS S3", + StorageProvider.CleuraS3 => "Cleura S3", + StorageProvider.MinIO => "MinIO", + _ => Link.Provider.ToString() + }; + } + + private static string GetFileIcon(string fileName) + { + string ext = Path.GetExtension(fileName).ToLowerInvariant(); + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".webp" || ext == ".svg" || ext == ".bmp") + return "bi bi-file-image"; + if (ext == ".pdf") + return "bi bi-file-pdf"; + if (ext == ".zip" || ext == ".tar" || ext == ".gz" || ext == ".7z" || ext == ".rar") + return "bi bi-file-zip"; + if (ext == ".mp4" || ext == ".avi" || ext == ".mov" || ext == ".mkv" || ext == ".webm") + return "bi bi-file-play"; + if (ext == ".mp3" || ext == ".wav" || ext == ".ogg" || ext == ".flac") + return "bi bi-file-music"; + if (ext == ".json" || ext == ".xml" || ext == ".yaml" || ext == ".yml" || ext == ".toml") + return "bi bi-file-code"; + if (ext == ".cs" || ext == ".js" || ext == ".ts" || ext == ".py" || ext == ".go" || ext == ".rs") + return "bi bi-file-code"; + if (ext == ".txt" || ext == ".md" || ext == ".log") + return "bi bi-file-text"; + if (ext == ".csv" || ext == ".xlsx" || ext == ".xls") + return "bi bi-file-spreadsheet"; + return "bi bi-file-earmark"; + } + + private static string FormatSize(long bytes) + { + if (bytes < 1024) return $"{bytes} B"; + if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / 1024.0 / 1024.0:F1} MB"; + return $"{bytes / 1024.0 / 1024.0 / 1024.0:F1} GB"; + } + + private static string GetContentType(string fileName) + { + string ext = Path.GetExtension(fileName).ToLowerInvariant(); + if (ext == ".jpg" || ext == ".jpeg") return "image/jpeg"; + if (ext == ".png") return "image/png"; + if (ext == ".gif") return "image/gif"; + if (ext == ".webp") return "image/webp"; + if (ext == ".svg") return "image/svg+xml"; + if (ext == ".pdf") return "application/pdf"; + if (ext == ".json") return "application/json"; + if (ext == ".xml") return "application/xml"; + if (ext == ".txt" || ext == ".md" || ext == ".log") return "text/plain"; + if (ext == ".csv") return "text/csv"; + if (ext == ".html" || ext == ".htm") return "text/html"; + if (ext == ".css") return "text/css"; + if (ext == ".js") return "text/javascript"; + if (ext == ".zip") return "application/zip"; + if (ext == ".gz") return "application/gzip"; + return "application/octet-stream"; + } + + private class BreadcrumbItem + { + public string Label { get; set; } = ""; + public string Path { get; set; } = ""; + public bool IsLast { get; set; } + } +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor.js b/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor.js new file mode 100644 index 0000000..f41143b --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/StorageBrowser.razor.js @@ -0,0 +1,20 @@ +export async function copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + return false; + } +} + +export function downloadFile(fileName, contentType, byteArray) { + const blob = new Blob([new Uint8Array(byteArray)], { type: contentType }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = fileName; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + URL.revokeObjectURL(url); +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/StorageTab.razor b/src/EntKube.Web/Components/Pages/Tenants/StorageTab.razor index d42cec8..7d7216f 100644 --- a/src/EntKube.Web/Components/Pages/Tenants/StorageTab.razor +++ b/src/EntKube.Web/Components/Pages/Tenants/StorageTab.razor @@ -1,6 +1,7 @@ @using EntKube.Web.Data @using EntKube.Web.Services @inject StorageService StorageService +@inject StorageBrowserService BrowserService @* =========================================================================== Storage Tab — shows MinIO instances discovered on clusters, plus external @@ -63,7 +64,7 @@ else @instance.Name
- @(GetMinioStatusBadge(instance.Status)) + @(GetMinioStatusBadge(instance))
@@ -89,13 +90,108 @@ else
}
+ + @* ── Pod status ── *@ + @if (instance.Pods.Count > 0) + { +
+
Pods
+
+ @foreach (PodInfo pod in instance.Pods) + { + bool allReady = pod.ReadyContainers == pod.TotalContainers && pod.TotalContainers > 0; + string podBadgeClass = pod.Status == "Running" && allReady + ? "bg-success-subtle text-success border border-success-subtle" + : pod.Status is "Pending" or "ContainerCreating" + ? "bg-warning-subtle text-warning border border-warning-subtle" + : "bg-danger-subtle text-danger border border-danger-subtle"; + + @pod.Name + @pod.ReadyContainers/@pod.TotalContainers + + } +
+
+ } + else if (instance.HealthStatus is not null) + { +
+ No pods found (label v1.min.io/tenant=@instance.Name) +
+ } + + @* ── Register as Storage Link ── *@ + @if (registeringInstance == instance) + { +
+
Register as Storage Link
+
+ +
+
+ + +
+ Use an externally accessible URL — the internal cluster address + (@instance.Endpoint) is blocked by network policy. +
+
+
+ +
+
+ +
+ @if (!string.IsNullOrEmpty(regFormError)) + { +
+ @regFormError +
+ } +
+ + +
+
+ } + + - @if (instance.CreatedAt.HasValue) - { - - } } @@ -185,6 +281,14 @@ else } + @if (link.BucketName is not null) + { + + } + + @if (link.BucketName is not null) + { + + } + + } else { + @if (link.BucketName is not null) + { + + } @@ -416,6 +561,14 @@ else } + @* ── File Browser ── *@ + @if (fileBrowserLink is not null) + { + + } + @* ── Add Storage Link Form ── *@ @if (showForm) { @@ -697,6 +850,15 @@ else private bool minioAvailable; private List minioInstances = []; + // MinIO registration form + private MinioBucketInfo? registeringInstance; + private string regFormName = ""; + private string regFormEndpoint = ""; + private string regFormAccessKey = ""; + private string regFormSecretKey = ""; + private bool registering; + private string? regFormError; + // External storage links private List storageLinks = []; private List environments = []; @@ -741,6 +903,9 @@ else private int corsMaxAge = 3600; private string bucketPolicyJson = ""; + // File browser state + private StorageLink? fileBrowserLink; + // Browse buckets state private StorageLink? browsedLink; private Guid? browsingLinkId; @@ -951,7 +1116,9 @@ else try { - browsedBuckets = await StorageService.ListCleuraS3BucketsAsync(TenantId, link.Id); + browsedBuckets = link.Provider == StorageProvider.MinIO + ? await StorageService.ListS3BucketsAsync(TenantId, link.Id) + : await StorageService.ListCleuraS3BucketsAsync(TenantId, link.Id); } catch (Exception ex) { @@ -970,6 +1137,18 @@ else browseError = null; } + // ── File Browser ── + + private void OpenFileBrowser(StorageLink link) + { + fileBrowserLink = link; + } + + private void CloseFileBrowser() + { + fileBrowserLink = null; + } + private async Task LoadCors() { if (managedLink is null) @@ -1215,6 +1394,72 @@ else } } + // ── MinIO Registration ── + + private void ShowRegisterForm(MinioBucketInfo instance) + { + registeringInstance = instance; + regFormName = instance.Name; + regFormEndpoint = ""; // leave blank so the user provides an externally accessible URL + regFormAccessKey = ""; + regFormSecretKey = ""; + regFormError = null; + } + + private void CancelRegisterForm() + { + registeringInstance = null; + regFormError = null; + } + + private async Task RegisterMinioTenant(MinioBucketInfo instance) + { + if (string.IsNullOrWhiteSpace(regFormName) || string.IsNullOrWhiteSpace(regFormEndpoint) + || string.IsNullOrWhiteSpace(regFormAccessKey) || string.IsNullOrWhiteSpace(regFormSecretKey)) + { + regFormError = "Name, endpoint URL, access key and secret key are all required."; + return; + } + + registering = true; + regFormError = null; + StateHasChanged(); + + try + { + // Use the environment from the cluster if we can match it; fall back to the first environment. + Guid envId = environments.FirstOrDefault(e => e.Name == instance.EnvironmentName)?.Id + ?? (environments.Count > 0 ? environments[0].Id : Guid.Empty); + + await StorageService.RegisterMinioTenantAsync( + TenantId, + envId, + instance.ClusterId, + regFormName.Trim(), + regFormEndpoint.Trim(), + regFormAccessKey.Trim(), + regFormSecretKey.Trim(), + notes: null); + + registeringInstance = null; + storageLinks = await StorageService.GetStorageLinksAsync(TenantId); + } + catch (Exception ex) + { + regFormError = ex.Message; + } + finally + { + registering = false; + } + } + + private bool IsMinioRegistered(MinioBucketInfo instance) => + storageLinks.Any(l => + l.Provider == StorageProvider.MinIO && + l.Component?.ClusterId == instance.ClusterId && + string.Equals(l.Endpoint, instance.Endpoint, StringComparison.OrdinalIgnoreCase)); + // ── Refresh + Helpers ── private async Task RefreshAll() @@ -1293,23 +1538,28 @@ else _ => provider.ToString() }; - private static RenderFragment GetMinioStatusBadge(string? status) => (status ?? "").ToLowerInvariant() switch + private static RenderFragment GetMinioStatusBadge(MinioBucketInfo instance) { - "initialized" or "running" or "ready" => __builder => - { - Ready - }, - "provisioning" or "waiting" => __builder => - { - Provisioning - }, - "error" or "failed" => __builder => - { - Failed - }, - _ => __builder => - { - @status - } - }; + // healthStatus ("green"/"yellow"/"red") is the operator's authoritative health signal. + // Fall back to Contains matching on currentState for operators that don't set it. + string h = (instance.HealthStatus ?? "").ToLowerInvariant(); + string s = (instance.Status ?? "").ToLowerInvariant(); + + bool isReady = h == "green" + || (h == "" && (s.Contains("initialized") || s.Contains("ready") || s.Contains("running"))); + + bool isFailed = h == "red" + || (h == "" && (s.Contains("error") || s.Contains("failed"))); + + if (isReady) + return __builder => { Ready }; + + if (isFailed) + return __builder => { Failed }; + + if (h == "yellow" || s.Contains("provisioning") || s.Contains("waiting") || s.Contains("certification")) + return __builder => { Provisioning }; + + return __builder => { @instance.Status }; + } } diff --git a/src/EntKube.Web/Components/Pages/Tenants/TenantDetail.razor b/src/EntKube.Web/Components/Pages/Tenants/TenantDetail.razor index a5cd41e..d142346 100644 --- a/src/EntKube.Web/Components/Pages/Tenants/TenantDetail.razor +++ b/src/EntKube.Web/Components/Pages/Tenants/TenantDetail.razor @@ -4,6 +4,8 @@ @using Microsoft.EntityFrameworkCore @rendermode InteractiveServer @inject TenantService TenantService +@inject UserAccessService UserAccessService +@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager Navigation @(tenant?.Name ?? "Tenant") @@ -68,6 +70,56 @@ else Identity + + + + + + + + + +
@@ -94,6 +146,36 @@ else case "identity": break; + case "registry": + + break; + case "messaging": + + break; + case "cache": + + break; + case "overview": + + break; + case "incidents": + + break; + case "notifications": + + break; + case "maintenance": + + break; + case "sla": + + break; + case "networking": + + break; + case "git": + + break; }
} @@ -106,11 +188,35 @@ else protected override async Task OnInitializedAsync() { + AuthenticationState authState = await AuthStateProvider.GetAuthenticationStateAsync(); + System.Security.Claims.ClaimsPrincipal user = authState.User; + + if (user.Identity?.IsAuthenticated != true) + { + Navigation.NavigateTo("/Account/Login", forceLoad: true); + return; + } + + string? userId = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (userId is null) + { + Navigation.NavigateTo("/Account/Login", forceLoad: true); + return; + } + tenant = await TenantService.GetTenantBySlugAsync(Slug); if (tenant is null) { Navigation.NavigateTo("/tenants"); + return; + } + + bool canAccess = await UserAccessService.HasTenantAccessAsync(userId, tenant.Id); + if (!canAccess) + { + bool hasMembership = await UserAccessService.HasAnyTenantMembershipAsync(userId); + Navigation.NavigateTo(hasMembership ? "/tenants" : "/portal"); } } } \ No newline at end of file diff --git a/src/EntKube.Web/Components/Pages/Tenants/TenantList.razor b/src/EntKube.Web/Components/Pages/Tenants/TenantList.razor index 44ce6cc..5e79693 100644 --- a/src/EntKube.Web/Components/Pages/Tenants/TenantList.razor +++ b/src/EntKube.Web/Components/Pages/Tenants/TenantList.razor @@ -4,96 +4,114 @@ @using Microsoft.EntityFrameworkCore @rendermode InteractiveServer @inject TenantService TenantService +@inject UserAccessService UserAccessService +@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager Navigation Tenants -
-
-

Tenants

-

Organizations and workspaces in your platform.

-
-
- -
-
-
- - - -
- @if (!string.IsNullOrEmpty(errorMessage)) - { -
@errorMessage
- } -
-
- -@if (tenants is null) +@if (!loaded) {

Loading tenants...

} -else if (tenants.Count == 0) -{ -
- -
No tenants yet
-

Create your first tenant above to get started.

-
-} else { -
- @foreach (Tenant tenant in tenants) - { -
-
-
-
-
-
- @tenant.Name -
-

- @tenant.Slug -

-
- -
-
+
+
+

Tenants

+

Organizations and workspaces in your platform.

+
+
- @if (confirmDeleteId == tenant.Id) - { -
-
- Delete this tenant? + @if (isAdmin) + { +
+
+
+ + + +
+ @if (!string.IsNullOrEmpty(errorMessage)) + { +
@errorMessage
+ } +
+
+ } + + @if (tenants is null || tenants.Count == 0) + { +
+ +
No tenants yet
+ @if (isAdmin) + { +

Create your first tenant above to get started.

+ } + else + { +

You have not been added to any tenants yet.

+ } +
+ } + else + { +
+ @foreach (Tenant tenant in tenants) + { +
+
+
+
- - +
+ @tenant.Name +
+

+ @tenant.Slug +

+
+ @if (isAdmin) + { + + } +
+
+ + @if (isAdmin && confirmDeleteId == tenant.Id) + { +
+
+ Delete this tenant? +
+ + +
-
- } + } -
-
- } -
+ } +
+ } } @code { @@ -101,19 +119,50 @@ else private string newTenantName = ""; private string? errorMessage; private Guid? confirmDeleteId; + private bool isAdmin; + private bool loaded; + private string? userId; protected override async Task OnInitializedAsync() { + AuthenticationState authState = await AuthStateProvider.GetAuthenticationStateAsync(); + System.Security.Claims.ClaimsPrincipal user = authState.User; + + if (user.Identity?.IsAuthenticated != true) + { + Navigation.NavigateTo("/Account/Login", forceLoad: true); + return; + } + + userId = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (userId is null) + { + Navigation.NavigateTo("/Account/Login", forceLoad: true); + return; + } + + isAdmin = await UserAccessService.IsGlobalAdminAsync(userId); + bool hasMembership = isAdmin || await UserAccessService.HasAnyTenantMembershipAsync(userId); + + if (!hasMembership) + { + Navigation.NavigateTo("/portal"); + return; + } + await LoadTenants(); + loaded = true; } private async Task LoadTenants() { - tenants = await TenantService.GetAllTenantsAsync(); + if (userId is null) return; + tenants = await UserAccessService.GetAccessibleTenantsAsync(userId); } private async Task CreateTenant() { + if (!isAdmin) return; errorMessage = null; try @@ -130,8 +179,9 @@ else private async Task DeleteTenant(Guid id) { + if (!isAdmin) return; confirmDeleteId = null; await TenantService.DeleteTenantAsync(id); await LoadTenants(); } -} \ No newline at end of file +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/TenantMonitoringOverview.razor b/src/EntKube.Web/Components/Pages/Tenants/TenantMonitoringOverview.razor new file mode 100644 index 0000000..6cf6de3 --- /dev/null +++ b/src/EntKube.Web/Components/Pages/Tenants/TenantMonitoringOverview.razor @@ -0,0 +1,200 @@ +@using EntKube.Web.Data +@using EntKube.Web.Services +@using Microsoft.EntityFrameworkCore +@inject PrometheusService PrometheusService +@inject IncidentService IncidentService +@inject IDbContextFactory DbFactory +@implements IAsyncDisposable + +
+
+ @if (lastRefreshed.HasValue) + { + Last refreshed: @lastRefreshed.Value.ToLocalTime().ToString("HH:mm:ss") + } +
+ +
+ +@if (clusterSummaries.Count > 0) +{ +
+
+
+
@clusterSummaries.Count
+
Clusters
+
+
+
+
+
@clusterSummaries.Sum(s => s.Health?.TotalNodes ?? 0)
+
Nodes
+
+
+
+
+
@clusterSummaries.Sum(s => s.Health?.RunningPods ?? 0)
+
Running Pods
+
+
+
+
+
@tenantActiveAlerts
+
Active Alerts
+
+
+
+} + +
+ @foreach (ClusterSummaryVm vm in clusterSummaries) + { +
+
+
+ + @vm.Cluster.Name + @if (vm.ActiveAlerts > 0) + { + @vm.ActiveAlerts alert@(vm.ActiveAlerts == 1 ? "" : "s") + } + else + { + Healthy + } +
+
+ @if (vm.Health is null) + { +
+ + @(vm.Error ?? "Prometheus not available") +
+ } + else + { +
+
+
CPU
+
+
+
+
@vm.Health.CpuUsagePercent.ToString("F1")%
+
+
+
Memory
+
+
+
+
@vm.Health.MemoryUsagePercent.ToString("F1")%
+
+
+
+ @vm.Health.ReadyNodes/@vm.Health.TotalNodes nodes ready +  ·  + @vm.Health.RunningPods pods running +
+ } +
+ +
+
+ } +
+ +@code { + [Parameter] public required Guid TenantId { get; set; } + [Parameter] public required string TenantSlug { get; set; } + + private List clusterSummaries = []; + private int tenantActiveAlerts; + private DateTime? lastRefreshed; + private bool isLoading; + private Timer? refreshTimer; + + protected override async Task OnInitializedAsync() + { + await Refresh(); + refreshTimer = new Timer(_ => InvokeAsync(async () => + { + await Refresh(); + StateHasChanged(); + }), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + } + + private async Task Refresh() + { + isLoading = true; + StateHasChanged(); + + List clusters; + using (ApplicationDbContext db = DbFactory.CreateDbContext()) + { + clusters = await db.KubernetesClusters + .Include(c => c.Components) + .Where(c => c.TenantId == TenantId) + .OrderBy(c => c.Name) + .ToListAsync(); + } + + tenantActiveAlerts = await IncidentService.GetActiveAlertCountForTenantAsync(TenantId); + + List> tasks = clusters + .Select(c => LoadClusterSummaryAsync(c)) + .ToList(); + + clusterSummaries = [.. await Task.WhenAll(tasks)]; + + lastRefreshed = DateTime.UtcNow; + isLoading = false; + } + + private async Task LoadClusterSummaryAsync(KubernetesCluster cluster) + { + bool hasPrometheus = cluster.Components.Any(c => + c.HelmChartName == "kube-prometheus-stack" && c.Status == ComponentStatus.Installed); + + if (!hasPrometheus) + return new ClusterSummaryVm(cluster, null, null, 0); + + KubernetesOperationResult result = + await PrometheusService.GetClusterHealthAsync(cluster.Id); + + return new ClusterSummaryVm( + cluster, + result.IsSuccess ? result.Data : null, + result.IsSuccess ? null : result.Error, + 0); + } + + private static string UtilColor(double pct) => + pct >= 90 ? "danger" : pct >= 70 ? "warning" : "success"; + + private static string ClusterBorderColor(ClusterSummaryVm vm) + { + if (vm.ActiveAlerts > 0) return "danger"; + if (vm.Health is null) return "secondary"; + if (vm.Health.CpuUsagePercent >= 90 || vm.Health.MemoryUsagePercent >= 90) return "warning"; + return "success"; + } + + public async ValueTask DisposeAsync() + { + if (refreshTimer is not null) + await refreshTimer.DisposeAsync(); + } + + private record ClusterSummaryVm( + KubernetesCluster Cluster, + ClusterHealthSummary? Health, + string? Error, + int ActiveAlerts); +} diff --git a/src/EntKube.Web/Components/Pages/Tenants/VaultTab.razor b/src/EntKube.Web/Components/Pages/Tenants/VaultTab.razor index 171319f..5d14f90 100644 --- a/src/EntKube.Web/Components/Pages/Tenants/VaultTab.razor +++ b/src/EntKube.Web/Components/Pages/Tenants/VaultTab.razor @@ -1,9 +1,11 @@ @using EntKube.Web.Data @using EntKube.Web.Services @using Microsoft.EntityFrameworkCore +@using EntKube.Web.Components.Pages.Shared @inject VaultService VaultService @inject TenantService TenantService @inject StorageService StorageService +@inject CnpgService CnpgService
@@ -39,6 +41,12 @@ else + +
@@ -125,6 +133,43 @@ else } } + else if (scope == "cnpg") + { + @if (cnpgClusterOptions is not null && cnpgClusterOptions.Count > 0) + { +
+
+
+ + +
+
+
+ } + else + { +
+ +

No CNPG clusters found for this tenant.

+
+ } + } + + @* --- Docker Registries Panel --- *@ + @if (scope == "docker") + { + + } + @* --- Secrets Panel --- *@ @if (HasSelection()) { @@ -132,6 +177,14 @@ else
Secrets + @if (scope == "app" && secrets?.Any(s => s.SyncToKubernetes) == true) + { + + }
@* Add secret form *@ @@ -193,6 +246,10 @@ else
NameScopeK8s Sync K8s Secret Namespace
@secret.Name + @if (secret.CnpgDatabase is not null) + { + @secret.CnpgDatabase.Name + } + else + { + cluster + } + @if (secret.SyncToKubernetes) { @@ -278,11 +348,23 @@ else
-
+ @if (scope == "app" && clusterOptions is not null) + { +
+ +
+ } +
-
+
@@ -303,6 +385,10 @@ else
} + @if (syncOutput is not null) + { +
@syncOutput
+ }
} @@ -326,6 +412,13 @@ else private List<(string Label, Guid Id)>? storageLinkOptions; private Guid selectedStorageLinkId; + // CNPG cluster scope + private List<(string Label, Guid Id)>? cnpgClusterOptions; + private Guid selectedCnpgClusterId; + + // Cluster options (used in app-scope sync config) + private List<(string Label, Guid Id)>? clusterOptions; + // Secrets private List? secrets; private string newSecretName = ""; @@ -335,6 +428,11 @@ else private Guid? syncConfigSecretId; private string syncSecretName = ""; private string syncNamespace = ""; + private Guid syncClusterId; + + // Sync-to-K8s state (app scope) + private bool syncingSecrets; + private string? syncOutput; // Reveal/Edit state private Guid? revealedSecretId; @@ -376,6 +474,7 @@ else } List clusters = await TenantService.GetClustersAsync(TenantId); + clusterOptions = clusters.Select(c => (c.Name, c.Id)).ToList(); componentOptions = []; foreach (KubernetesCluster cluster in clusters) @@ -394,6 +493,13 @@ else storageLinkOptions = links .Select(l => ($"{l.Environment.Name} / {l.Name} ({l.Provider})", l.Id)) .ToList(); + + // Load CNPG clusters for the database secrets scope. + + List cnpgClusters = await CnpgService.GetClustersAsync(TenantId); + cnpgClusterOptions = cnpgClusters + .Select(c => ($"{c.KubernetesCluster.Name} / {c.Name}", c.Id)) + .ToList(); } private void SwitchScope(string newScope) @@ -403,15 +509,20 @@ else selectedAppId = Guid.Empty; selectedComponentId = Guid.Empty; selectedStorageLinkId = Guid.Empty; + selectedCnpgClusterId = Guid.Empty; errorMessage = null; successMessage = null; + syncClusterId = Guid.Empty; + syncOutput = null; + // Docker registries panel manages its own state; no reset needed here. } private bool HasSelection() { return (scope == "app" && selectedAppId != Guid.Empty) || (scope == "component" && selectedComponentId != Guid.Empty) - || (scope == "storage" && selectedStorageLinkId != Guid.Empty); + || (scope == "storage" && selectedStorageLinkId != Guid.Empty) + || (scope == "cnpg" && selectedCnpgClusterId != Guid.Empty); } private async Task LoadSecrets() @@ -431,6 +542,10 @@ else { secrets = await VaultService.GetStorageLinkSecretsAsync(TenantId, selectedStorageLinkId); } + else if (scope == "cnpg" && selectedCnpgClusterId != Guid.Empty) + { + secrets = await VaultService.GetAllCnpgSecretsForClusterAsync(TenantId, selectedCnpgClusterId); + } else { secrets = null; @@ -456,6 +571,10 @@ else { await VaultService.SetStorageLinkSecretAsync(TenantId, selectedStorageLinkId, newSecretName.Trim(), newSecretValue.Trim()); } + else if (scope == "cnpg") + { + await VaultService.SetCnpgClusterSecretAsync(TenantId, selectedCnpgClusterId, newSecretName.Trim(), newSecretValue.Trim()); + } successMessage = $"Secret '{newSecretName.Trim()}' saved."; newSecretName = ""; @@ -555,6 +674,7 @@ else syncConfigSecretId = secret.Id; syncSecretName = secret.KubernetesSecretName ?? ""; syncNamespace = secret.KubernetesNamespace ?? ""; + syncClusterId = secret.KubernetesClusterId ?? Guid.Empty; } } @@ -571,13 +691,34 @@ else return; } + Guid? clusterId = (scope == "app" && syncClusterId != Guid.Empty) ? syncClusterId : (Guid?)null; + await VaultService.ConfigureKubernetesSyncAsync( syncConfigSecretId.Value, syncEnabled: true, secretName: syncSecretName.Trim(), - ns: syncNamespace.Trim()); + ns: syncNamespace.Trim(), + clusterId: clusterId); syncConfigSecretId = null; await LoadSecrets(); } + + private async Task SyncAllAppSecrets() + { + if (selectedAppId == Guid.Empty) return; + + syncingSecrets = true; + syncOutput = null; + + try + { + HelmExecutionResult result = await VaultService.SyncAppSecretsToKubernetesAsync(TenantId, selectedAppId); + syncOutput = result.Output; + } + finally + { + syncingSecrets = false; + } + } } \ No newline at end of file diff --git a/src/EntKube.Web/Components/_Imports.razor b/src/EntKube.Web/Components/_Imports.razor index 2301c6c..79b37c8 100644 --- a/src/EntKube.Web/Components/_Imports.razor +++ b/src/EntKube.Web/Components/_Imports.razor @@ -11,3 +11,4 @@ @using EntKube.Web.Client @using EntKube.Web.Components @using EntKube.Web.Components.Layout +@using EntKube.Web.Components.Pages.Shared diff --git a/src/EntKube.Web/Data/AlertIncident.cs b/src/EntKube.Web/Data/AlertIncident.cs new file mode 100644 index 0000000..2ba4d2c --- /dev/null +++ b/src/EntKube.Web/Data/AlertIncident.cs @@ -0,0 +1,27 @@ +namespace EntKube.Web.Data; + +public enum IncidentStatus { Active, Acknowledged, Resolved } + +public class AlertIncident +{ + public Guid Id { get; set; } + public Guid ClusterId { get; set; } + public required string Fingerprint { get; set; } + public required string AlertName { get; set; } + public required string Severity { get; set; } + public string Summary { get; set; } = ""; + public string Description { get; set; } = ""; + public string LabelsJson { get; set; } = "{}"; + public DateTime StartsAt { get; set; } + public DateTime? EndsAt { get; set; } + public IncidentStatus Status { get; set; } = IncidentStatus.Active; + public string? AcknowledgedBy { get; set; } + public DateTime? AcknowledgedAt { get; set; } + public DateTime? ResolvedAt { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public KubernetesCluster Cluster { get; set; } = null!; + public List Notes { get; set; } = []; + public List Deliveries { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/App.cs b/src/EntKube.Web/Data/App.cs index ee684b2..71686d2 100644 --- a/src/EntKube.Web/Data/App.cs +++ b/src/EntKube.Web/Data/App.cs @@ -15,6 +15,13 @@ public class App /// public required string Name { get; set; } + /// + /// The primary Kubernetes namespace for this app. Used as the target for + /// ResourceQuota, NetworkPolicy, and RBAC resources. Set by tenant admins; + /// read-only in the customer portal. Null means no namespace has been assigned. + /// + public string? Namespace { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // Navigation @@ -22,4 +29,7 @@ public class App public ICollection AppEnvironments { get; set; } = []; public ICollection Secrets { get; set; } = []; public ICollection Deployments { get; set; } = []; + public AppQuota? Quota { get; set; } + public ICollection NetworkPolicies { get; set; } = []; + public AppRbacPolicy? RbacPolicy { get; set; } } diff --git a/src/EntKube.Web/Data/AppDeployment.cs b/src/EntKube.Web/Data/AppDeployment.cs index 8077106..8664f44 100644 --- a/src/EntKube.Web/Data/AppDeployment.cs +++ b/src/EntKube.Web/Data/AppDeployment.cs @@ -79,12 +79,57 @@ public class AppDeployment public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + // ── Git source fields (only used when Type is GitYaml, GitHelm, or GitAppOfApps) ── + + /// + /// The registered GitRepository to sync from. + /// + public Guid? GitRepositoryId { get; set; } + + /// + /// Path within the repository to the manifest directory, Helm chart root, + /// or app-of-apps directory. Use "." for the repo root. + /// + public string? GitPath { get; set; } + + /// + /// Branch, tag, or commit SHA to check out. Defaults to the repository's + /// DefaultBranch when null. + /// + public string? GitRevision { get; set; } + + /// + /// SHA of the commit that was last successfully synced from Git. + /// + public string? GitLastSyncedCommit { get; set; } + + /// + /// When the last successful Git sync completed. + /// + public DateTime? GitLastSyncedAt { get; set; } + + /// + /// When true the background GitSyncService will poll this deployment + /// and sync whenever a new commit is detected. + /// + public bool GitAutoSync { get; set; } = true; + + /// + /// For GitAppOfApps child deployments: the parent deployment that manages + /// this deployment's lifecycle. Null for top-level deployments. + /// + public Guid? ParentDeploymentId { get; set; } + // Navigation public App App { get; set; } = null!; public Environment Environment { get; set; } = null!; public KubernetesCluster Cluster { get; set; } = null!; + public GitRepository? GitRepository { get; set; } + public AppDeployment? ParentDeployment { get; set; } + public ICollection ChildDeployments { get; set; } = []; public ICollection Manifests { get; set; } = []; public ICollection Resources { get; set; } = []; public ICollection StorageBindings { get; set; } = []; public ICollection DatabaseBindings { get; set; } = []; + public ICollection CacheBindings { get; set; } = []; } diff --git a/src/EntKube.Web/Data/AppNetworkPolicy.cs b/src/EntKube.Web/Data/AppNetworkPolicy.cs new file mode 100644 index 0000000..ee73625 --- /dev/null +++ b/src/EntKube.Web/Data/AppNetworkPolicy.cs @@ -0,0 +1,44 @@ +namespace EntKube.Web.Data; + +public enum AppNetworkPolicyType +{ + /// Deny all ingress and egress traffic (security baseline). + DenyAll, + + /// Allow ingress from the ingress controller namespace. + AllowFromIngress, + + /// Allow ingress from pods in the same namespace. + AllowFromSameNamespace, + + /// Allow ingress from a specific namespace (see AllowFromNamespace). + AllowFromNamespace, + + /// Custom YAML policy — see CustomYaml. + Custom +} + +/// +/// A NetworkPolicy applied to the app's namespace. Multiple policies can +/// coexist (e.g. DenyAll + AllowFromIngress is a common baseline). +/// Applied via kubectl apply; managed by tenant admins and read-only in the portal. +/// +public class AppNetworkPolicy +{ + public Guid Id { get; set; } + public Guid AppId { get; set; } + + /// Human-readable name and also the K8s NetworkPolicy resource name. + public required string Name { get; set; } + + public AppNetworkPolicyType PolicyType { get; set; } + + /// For AllowFromNamespace: the source namespace to allow. + public string? AllowFromNamespace { get; set; } + + /// For Custom: the complete NetworkPolicy YAML document. + public string? CustomYaml { get; set; } + + // Navigation + public App App { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/AppQuota.cs b/src/EntKube.Web/Data/AppQuota.cs new file mode 100644 index 0000000..e82744d --- /dev/null +++ b/src/EntKube.Web/Data/AppQuota.cs @@ -0,0 +1,29 @@ +namespace EntKube.Web.Data; + +/// +/// Resource quota limits for an app's namespace, applied as a Kubernetes +/// ResourceQuota. One record per app (1:1). Null fields are omitted from the +/// generated manifest so only the desired constraints are enforced. +/// +public class AppQuota +{ + public Guid Id { get; set; } + public Guid AppId { get; set; } + + // CPU + public string? CpuRequest { get; set; } // e.g. "500m" + public string? CpuLimit { get; set; } // e.g. "2" + + // Memory + public string? MemoryRequest { get; set; } // e.g. "256Mi" + public string? MemoryLimit { get; set; } // e.g. "1Gi" + + // Count quotas + public int? MaxPods { get; set; } + public int? MaxPvcs { get; set; } + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public App App { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/AppRbacPolicy.cs b/src/EntKube.Web/Data/AppRbacPolicy.cs new file mode 100644 index 0000000..879f675 --- /dev/null +++ b/src/EntKube.Web/Data/AppRbacPolicy.cs @@ -0,0 +1,52 @@ +namespace EntKube.Web.Data; + +/// +/// RBAC configuration for an app: a ServiceAccount plus a Role/RoleBinding pair. +/// One record per app (1:1). When applied, creates: +/// - ServiceAccount (ServiceAccountName in the app's namespace) +/// - Role (ServiceAccountName + "-role" with all Rules) +/// - RoleBinding (ServiceAccountName + "-binding" → that Role) +/// +public class AppRbacPolicy +{ + public Guid Id { get; set; } + public Guid AppId { get; set; } + + /// + /// The Kubernetes ServiceAccount name, e.g. "billing-api". + /// Must be a valid DNS label. + /// + public required string ServiceAccountName { get; set; } + + /// + /// Whether pods should auto-mount the service account token. + /// Defaults to false (least-privilege). + /// + public bool AutoMountToken { get; set; } = false; + + // Navigation + public App App { get; set; } = null!; + public ICollection Rules { get; set; } = []; +} + +/// +/// A single rule inside an AppRbacPolicy's Role, equivalent to one entry in +/// rules[] of a Kubernetes Role manifest. +/// +public class AppRbacRule +{ + public Guid Id { get; set; } + public Guid AppRbacPolicyId { get; set; } + + /// API group, e.g. "" (core), "apps", "batch", or "*". + public string ApiGroups { get; set; } = ""; + + /// Comma-separated resource types, e.g. "pods,services" or "*". + public required string Resources { get; set; } + + /// Comma-separated verbs, e.g. "get,list,watch" or "*". + public required string Verbs { get; set; } + + // Navigation + public AppRbacPolicy Policy { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/ApplicationDbContext.cs b/src/EntKube.Web/Data/ApplicationDbContext.cs index f1d4ade..8044bf3 100644 --- a/src/EntKube.Web/Data/ApplicationDbContext.cs +++ b/src/EntKube.Web/Data/ApplicationDbContext.cs @@ -42,7 +42,37 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< public DbSet MongoBackups => Set(); public DbSet KeycloakComponentConfigs => Set(); public DbSet KeycloakRealms => Set(); + public DbSet KeycloakThemes => Set(); public DbSet KeycloakBackups => Set(); + public DbSet RegisteredPostgresInstances => Set(); + public DbSet RegisteredPostgresDatabases => Set(); + public DbSet RegisteredPostgresDumps => Set(); + public DbSet HarborComponentConfigs => Set(); + public DbSet HarborProjects => Set(); + public DbSet DockerRegistryCredentials => Set(); + public DbSet AuditEvents => Set(); + public DbSet RabbitMQClusters => Set(); + public DbSet RabbitMQBackups => Set(); + public DbSet MessagingBindings => Set(); + public DbSet AlertIncidents => Set(); + public DbSet IncidentNotes => Set(); + public DbSet NotificationChannels => Set(); + public DbSet NotificationDeliveries => Set(); + public DbSet DeploymentHealthSnapshots => Set(); + public DbSet MaintenanceWindows => Set(); + public DbSet SlaTargets => Set(); + public DbSet ExternalRouteHealthHistories => Set(); + public DbSet VpnTunnels => Set(); + public DbSet VpnLocalEndpoints => Set(); + public DbSet VpnRemoteEndpoints => Set(); + public DbSet RedisClusters => Set(); + public DbSet CacheBindings => Set(); + public DbSet GitRepositories => Set(); + public DbSet GitKnownHosts => Set(); + public DbSet AppQuotas => Set(); + public DbSet AppNetworkPolicies => Set(); + public DbSet AppRbacPolicies => Set(); + public DbSet AppRbacRules => Set(); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -268,6 +298,11 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< .HasForeignKey(s => s.ComponentId) .OnDelete(DeleteBehavior.Cascade); + entity.HasOne(s => s.CnpgCluster) + .WithMany() + .HasForeignKey(s => s.CnpgClusterId) + .OnDelete(DeleteBehavior.Cascade); + entity.HasOne(s => s.CnpgDatabase) .WithMany() .HasForeignKey(s => s.CnpgDatabaseId) @@ -282,6 +317,57 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< .WithMany() .HasForeignKey(s => s.MongoClusterId) .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(s => s.RegisteredPostgresDatabase) + .WithMany() + .HasForeignKey(s => s.RegisteredPostgresDatabaseId) + .OnDelete(DeleteBehavior.Cascade); + + // App-scoped secrets can optionally specify a target K8s cluster for sync. + // When the cluster is deleted the FK is set to null (secrets are not removed). + entity.HasOne(s => s.KubernetesCluster) + .WithMany() + .HasForeignKey(s => s.KubernetesClusterId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(s => s.RabbitMQCluster) + .WithMany() + .HasForeignKey(s => s.RabbitMQClusterId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(s => s.RedisCluster) + .WithMany() + .HasForeignKey(s => s.RedisClusterId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // DockerRegistryCredential — encrypted registry auth stored in the tenant vault. + // Password is AES-256-GCM encrypted with the tenant DEK. + + builder.Entity(entity => + { + entity.HasKey(d => d.Id); + entity.Property(d => d.Name).HasMaxLength(200).IsRequired(); + entity.Property(d => d.Server).HasMaxLength(500).IsRequired(); + entity.Property(d => d.Username).HasMaxLength(300).IsRequired(); + entity.Property(d => d.Email).HasMaxLength(254); + entity.Property(d => d.KubernetesSecretName).HasMaxLength(253); + entity.Property(d => d.KubernetesNamespace).HasMaxLength(63); + + entity.HasOne(d => d.Vault) + .WithMany() + .HasForeignKey(d => d.VaultId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(d => d.App) + .WithMany() + .HasForeignKey(d => d.AppId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(d => d.KubernetesCluster) + .WithMany() + .HasForeignKey(d => d.KubernetesClusterId) + .OnDelete(DeleteBehavior.SetNull); }); // ClusterComponent — a deployable unit (Helm chart, operator, etc.) on a cluster. @@ -448,6 +534,7 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< entity.Property(c => c.PostgresVersion).HasMaxLength(10).IsRequired(); entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired(); entity.Property(c => c.BackupSchedule).HasMaxLength(100); + entity.Property(c => c.MaxBackups).HasDefaultValue(20); entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique(); @@ -502,6 +589,7 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< entity.Property(c => c.MongoVersion).HasMaxLength(20).IsRequired(); entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired(); entity.Property(c => c.BackupSchedule).HasMaxLength(100); + entity.Property(c => c.MaxBackups).HasDefaultValue(20); entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique(); @@ -556,6 +644,7 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< entity.HasKey(c => c.Id); entity.Property(c => c.AdminUsername).HasMaxLength(100).IsRequired(); entity.Property(c => c.AdminUrl).HasMaxLength(500); + entity.Property(c => c.DisplayName).HasMaxLength(200); entity.HasOne(c => c.Tenant) .WithMany() @@ -565,12 +654,37 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< entity.HasOne(c => c.ClusterComponent) .WithMany() .HasForeignKey(c => c.ClusterComponentId) + .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(c => c.CnpgDatabase) .WithMany() .HasForeignKey(c => c.CnpgDatabaseId) .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(c => c.RegisteredPostgresDatabase) + .WithMany() + .HasForeignKey(c => c.RegisteredPostgresDatabaseId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // KeycloakTheme — a named CSS theme bundle for a Keycloak instance. + // Name must be unique within the instance. CSS is stored separately in + // the vault (keyed by theme Id). Multiple realms can reference the same theme. + + builder.Entity(entity => + { + entity.HasKey(t => t.Id); + entity.Property(t => t.Name).HasMaxLength(200).IsRequired(); + entity.Property(t => t.LoginTheme).HasMaxLength(100); + entity.Property(t => t.AccountTheme).HasMaxLength(100); + + entity.HasIndex(t => new { t.KeycloakComponentConfigId, t.Name }).IsUnique(); + + entity.HasOne(t => t.ComponentConfig) + .WithMany() + .HasForeignKey(t => t.KeycloakComponentConfigId) + .OnDelete(DeleteBehavior.Cascade); }); // KeycloakRealm — a realm managed via a component config. RealmName must be @@ -593,6 +707,11 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< .WithMany() .HasForeignKey(r => r.LinkedAppId) .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(r => r.Theme) + .WithMany(t => t.Realms) + .HasForeignKey(r => r.KeycloakThemeId) + .OnDelete(DeleteBehavior.SetNull); }); // KeycloakBackup — realm JSON snapshots stored in S3. @@ -611,12 +730,69 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< entity.HasOne(b => b.StorageLink) .WithMany() .HasForeignKey(b => b.StorageLinkId) - .OnDelete(DeleteBehavior.Restrict); + .OnDelete(DeleteBehavior.SetNull); }); - // DatabaseBinding — connects a managed database (CNPG or MongoDB) to an - // AppDeployment. The platform syncs credentials into the app's namespace - // automatically, including after password rotations. + // RegisteredPostgresInstance — a vanilla Postgres server inside a K8s cluster + // that is not managed by CNPG. EntKube registers it to manage databases and + // credentials, but does not own the server lifecycle. + + builder.Entity(entity => + { + entity.HasKey(i => i.Id); + entity.Property(i => i.Name).HasMaxLength(200).IsRequired(); + entity.Property(i => i.Namespace).HasMaxLength(63).IsRequired(); + entity.Property(i => i.ServiceName).HasMaxLength(253).IsRequired(); + entity.Property(i => i.AdminPodName).HasMaxLength(253).IsRequired(); + entity.Property(i => i.AdminUsername).HasMaxLength(63).IsRequired(); + entity.Property(i => i.Notes).HasMaxLength(1000); + + entity.HasIndex(i => new { i.TenantId, i.Name }).IsUnique(); + + entity.HasOne(i => i.Tenant) + .WithMany() + .HasForeignKey(i => i.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(i => i.KubernetesCluster) + .WithMany() + .HasForeignKey(i => i.KubernetesClusterId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(d => d.Id); + entity.Property(d => d.Name).HasMaxLength(63).IsRequired(); + entity.Property(d => d.Owner).HasMaxLength(63).IsRequired(); + + entity.HasIndex(d => new { d.RegisteredPostgresInstanceId, d.Name }).IsUnique(); + + entity.HasOne(d => d.RegisteredPostgresInstance) + .WithMany(i => i.Databases) + .HasForeignKey(d => d.RegisteredPostgresInstanceId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(d => d.Id); + entity.Property(d => d.S3Key).HasMaxLength(1024).IsRequired(); + + entity.HasOne(d => d.RegisteredPostgresDatabase) + .WithMany() + .HasForeignKey(d => d.RegisteredPostgresDatabaseId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(d => d.StorageLink) + .WithMany() + .HasForeignKey(d => d.StorageLinkId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // DatabaseBinding — connects a managed database (CNPG, MongoDB, or registered + // Postgres) to an AppDeployment. The platform syncs credentials into the app's + // namespace automatically, including after password rotations. builder.Entity(entity => { @@ -633,11 +809,521 @@ public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext< .HasForeignKey(b => b.MongoDatabaseId) .OnDelete(DeleteBehavior.Cascade); + entity.HasOne(b => b.RegisteredPostgresDatabase) + .WithMany(d => d.DatabaseBindings) + .HasForeignKey(b => b.RegisteredPostgresDatabaseId) + .OnDelete(DeleteBehavior.Cascade); + entity.HasOne(b => b.AppDeployment) .WithMany(d => d.DatabaseBindings) .HasForeignKey(b => b.AppDeploymentId) .OnDelete(DeleteBehavior.Cascade); }); + + // HarborComponentConfig — links an installed Harbor ClusterComponent to its + // CNPG database and S3 storage link. One config per Harbor install. + // Admin password and S3/DB credentials are stored in the vault (not here). + + builder.Entity(entity => + { + entity.HasKey(a => a.Id); + entity.Property(a => a.Action).HasMaxLength(100).IsRequired(); + entity.Property(a => a.ResourceKind).HasMaxLength(100).IsRequired(); + entity.Property(a => a.ResourceName).HasMaxLength(253); + entity.Property(a => a.PerformedBy).HasMaxLength(256); + entity.Property(a => a.Details).HasMaxLength(1000); + + entity.HasIndex(a => a.DeploymentId); + entity.HasIndex(a => a.OccurredAt); + + entity.HasOne(a => a.Deployment) + .WithMany() + .HasForeignKey(a => a.DeploymentId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(c => c.Id); + entity.Property(c => c.AdminUsername).HasMaxLength(100).IsRequired(); + entity.Property(c => c.RegistryUrl).HasMaxLength(500); + + entity.HasOne(c => c.Tenant) + .WithMany() + .HasForeignKey(c => c.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(c => c.ClusterComponent) + .WithMany() + .HasForeignKey(c => c.ClusterComponentId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(c => c.CnpgDatabase) + .WithMany() + .HasForeignKey(c => c.CnpgDatabaseId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(c => c.StorageLink) + .WithMany() + .HasForeignKey(c => c.StorageLinkId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // HarborProject — tracks a Harbor project managed by EntKube. + // Project name must be unique within a config (Harbor enforces this globally per server). + // LinkedAppId links the project to a customer app for portal self-service. + + builder.Entity(entity => + { + entity.HasKey(p => p.Id); + entity.Property(p => p.ProjectName).HasMaxLength(255).IsRequired(); + + entity.HasIndex(p => new { p.HarborComponentConfigId, p.ProjectName }).IsUnique(); + + entity.HasOne(p => p.Tenant) + .WithMany() + .HasForeignKey(p => p.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(p => p.HarborComponentConfig) + .WithMany() + .HasForeignKey(p => p.HarborComponentConfigId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(p => p.LinkedApp) + .WithMany() + .HasForeignKey(p => p.LinkedAppId) + .OnDelete(DeleteBehavior.SetNull); + }); + + builder.Entity(entity => + { + entity.HasKey(c => c.Id); + entity.Property(c => c.Name).HasMaxLength(63).IsRequired(); + entity.Property(c => c.Namespace).HasMaxLength(63).IsRequired(); + entity.Property(c => c.RabbitMQVersion).HasMaxLength(20).IsRequired(); + entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired(); + entity.Property(c => c.StorageClass).HasMaxLength(63); + entity.Property(c => c.BackupSchedule).HasMaxLength(100); + entity.Property(c => c.MaxBackups).HasDefaultValue(10); + + entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique(); + + entity.HasOne(c => c.Tenant) + .WithMany() + .HasForeignKey(c => c.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(c => c.KubernetesCluster) + .WithMany() + .HasForeignKey(c => c.KubernetesClusterId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(c => c.StorageLink) + .WithMany() + .HasForeignKey(c => c.StorageLinkId) + .OnDelete(DeleteBehavior.SetNull); + }); + + builder.Entity(entity => + { + entity.HasKey(b => b.Id); + entity.Property(b => b.ObjectKey).HasMaxLength(1024).IsRequired(); + entity.Property(b => b.ClusterName).HasMaxLength(63).IsRequired(); + + entity.HasOne(b => b.Cluster) + .WithMany(c => c.Backups) + .HasForeignKey(b => b.RabbitMQClusterId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(b => b.StorageLink) + .WithMany() + .HasForeignKey(b => b.StorageLinkId) + .OnDelete(DeleteBehavior.SetNull); + }); + + builder.Entity(entity => + { + entity.HasKey(c => c.Id); + entity.Property(c => c.Name).HasMaxLength(63).IsRequired(); + entity.Property(c => c.Namespace).HasMaxLength(63).IsRequired(); + entity.Property(c => c.RedisVersion).HasMaxLength(20).IsRequired(); + entity.Property(c => c.StorageSize).HasMaxLength(20).IsRequired(); + entity.Property(c => c.StorageClass).HasMaxLength(63); + + entity.HasIndex(c => new { c.KubernetesClusterId, c.Name, c.Namespace }).IsUnique(); + + entity.HasOne(c => c.Tenant) + .WithMany() + .HasForeignKey(c => c.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(c => c.KubernetesCluster) + .WithMany() + .HasForeignKey(c => c.KubernetesClusterId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(b => b.Id); + entity.Property(b => b.KubernetesSecretName).HasMaxLength(253).IsRequired(); + + entity.HasOne(b => b.RedisCluster) + .WithMany() + .HasForeignKey(b => b.RedisClusterId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(b => b.AppDeployment) + .WithMany(d => d.CacheBindings) + .HasForeignKey(b => b.AppDeploymentId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(i => i.Id); + entity.HasIndex(i => new { i.ClusterId, i.Fingerprint }).IsUnique(); + entity.HasIndex(i => i.Status); + entity.HasIndex(i => i.StartsAt); + entity.Property(i => i.Fingerprint).HasMaxLength(100).IsRequired(); + entity.Property(i => i.AlertName).HasMaxLength(200).IsRequired(); + entity.Property(i => i.Severity).HasMaxLength(20).IsRequired(); + entity.Property(i => i.Summary).HasMaxLength(500); + entity.Property(i => i.Description).HasMaxLength(2000); + entity.Property(i => i.AcknowledgedBy).HasMaxLength(256); + entity.Property(i => i.Status).HasConversion().HasMaxLength(20); + + entity.HasOne(i => i.Cluster) + .WithMany() + .HasForeignKey(i => i.ClusterId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(n => n.Id); + entity.HasIndex(n => n.IncidentId); + entity.Property(n => n.Author).HasMaxLength(256).IsRequired(); + entity.Property(n => n.Content).HasMaxLength(2000).IsRequired(); + + entity.HasOne(n => n.Incident) + .WithMany(i => i.Notes) + .HasForeignKey(n => n.IncidentId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(c => c.Id); + entity.HasIndex(c => new { c.TenantId, c.Name }).IsUnique(); + entity.Property(c => c.Name).HasMaxLength(200).IsRequired(); + entity.Property(c => c.Type).HasConversion().HasMaxLength(20); + entity.Property(c => c.SeverityFilter).HasConversion().HasMaxLength(30); + + entity.HasOne(c => c.Tenant) + .WithMany() + .HasForeignKey(c => c.TenantId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(d => d.Id); + entity.HasIndex(d => new { d.IncidentId, d.ChannelId, d.IsFiring }); + entity.Property(d => d.Error).HasMaxLength(1000); + + entity.HasOne(d => d.Incident) + .WithMany(i => i.Deliveries) + .HasForeignKey(d => d.IncidentId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(d => d.Channel) + .WithMany(c => c.Deliveries) + .HasForeignKey(d => d.ChannelId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(s => s.Id); + entity.HasIndex(s => new { s.DeploymentId, s.SnapshotAt }); + entity.Property(s => s.HealthStatus).HasConversion().HasMaxLength(20); + entity.Property(s => s.SyncStatus).HasConversion().HasMaxLength(20); + + entity.HasOne(s => s.Deployment) + .WithMany() + .HasForeignKey(s => s.DeploymentId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(w => w.Id); + entity.HasIndex(w => new { w.TenantId, w.StartsAt }); + entity.HasIndex(w => w.StartsAt); + entity.Property(w => w.Title).HasMaxLength(200).IsRequired(); + entity.Property(w => w.Description).HasMaxLength(1000); + entity.Property(w => w.CreatedBy).HasMaxLength(256).IsRequired(); + + entity.HasOne(w => w.Tenant) + .WithMany() + .HasForeignKey(w => w.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(w => w.Cluster) + .WithMany() + .HasForeignKey(w => w.ClusterId) + .OnDelete(DeleteBehavior.SetNull); + }); + + builder.Entity(entity => + { + entity.HasKey(s => s.Id); + entity.HasIndex(s => new { s.TenantId, s.CustomerId, s.AppId }).IsUnique().HasFilter(null); + + entity.HasOne(s => s.Tenant) + .WithMany() + .HasForeignKey(s => s.TenantId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(s => s.Customer) + .WithMany() + .HasForeignKey(s => s.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(s => s.App) + .WithMany() + .HasForeignKey(s => s.AppId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(h => h.Id); + entity.HasIndex(h => new { h.RouteId, h.CheckedAt }); + + entity.HasOne(h => h.Route) + .WithMany() + .HasForeignKey(h => h.RouteId) + .OnDelete(DeleteBehavior.Cascade); + }); + + builder.Entity(entity => + { + entity.HasKey(b => b.Id); + entity.Property(b => b.Vhost).HasMaxLength(200).IsRequired(); + entity.Property(b => b.QueueName).HasMaxLength(255); + entity.Property(b => b.ExchangeName).HasMaxLength(255); + entity.Property(b => b.KubernetesSecretName).HasMaxLength(253).IsRequired(); + + entity.HasOne(b => b.Cluster) + .WithMany() + .HasForeignKey(b => b.RabbitMQClusterId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(b => b.AppDeployment) + .WithMany() + .HasForeignKey(b => b.AppDeploymentId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // VpnTunnel — scoped to a tenant, name unique within a tenant. + + builder.Entity(entity => + { + entity.HasKey(t => t.Id); + entity.HasIndex(t => new { t.TenantId, t.Name }).IsUnique(); + entity.Property(t => t.Name).HasMaxLength(200).IsRequired(); + entity.Property(t => t.TunnelType).HasConversion().HasMaxLength(20); + entity.Property(t => t.Status).HasConversion().HasMaxLength(20); + entity.Property(t => t.IkeProposal).HasMaxLength(100).IsRequired(); + entity.Property(t => t.EspProposal).HasMaxLength(100).IsRequired(); + + entity.HasOne(t => t.Tenant) + .WithMany() + .HasForeignKey(t => t.TenantId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // VpnLocalEndpoint — a platform cluster participating in a VPN tunnel. + + builder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Subnets).HasMaxLength(1000).IsRequired(); + entity.Property(e => e.PublicIp).HasMaxLength(45); + entity.Property(e => e.Role).HasConversion().HasMaxLength(20); + entity.Property(e => e.Status).HasConversion().HasMaxLength(20); + + entity.HasOne(e => e.Tunnel) + .WithMany(t => t.LocalEndpoints) + .HasForeignKey(e => e.VpnTunnelId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.Cluster) + .WithMany() + .HasForeignKey(e => e.ClusterId) + .OnDelete(DeleteBehavior.Restrict); + + entity.HasOne(e => e.Component) + .WithMany() + .HasForeignKey(e => e.ComponentId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // VpnRemoteEndpoint — an external site for SiteToSite tunnels. PSK/cert stored + // as VaultSecret entries scoped to this endpoint via VpnRemoteEndpointId. + + builder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.PublicIp).HasMaxLength(45).IsRequired(); + entity.Property(e => e.Subnets).HasMaxLength(1000).IsRequired(); + entity.Property(e => e.AuthMode).HasConversion().HasMaxLength(20); + entity.Property(e => e.Status).HasConversion().HasMaxLength(20); + entity.Property(e => e.LocalId).HasMaxLength(500); + entity.Property(e => e.RemoteId).HasMaxLength(500); + + entity.HasOne(e => e.Tunnel) + .WithMany(t => t.RemoteEndpoints) + .HasForeignKey(e => e.VpnTunnelId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // VaultSecret — VpnRemoteEndpoint scoping (PSK/cert for a remote site). + // Registering this here keeps the VaultSecret entity config in one place + // even though this relationship is declared in the VpnRemoteEndpoint section. + builder.Entity() + .HasOne(s => s.VpnRemoteEndpoint) + .WithMany(e => e.Secrets) + .HasForeignKey(s => s.VpnRemoteEndpointId) + .OnDelete(DeleteBehavior.Cascade); + + // VaultSecret — GitRepository scoping (PAT / SSH key / password for a git repo). + builder.Entity() + .HasOne(s => s.GitRepository) + .WithMany() + .HasForeignKey(s => s.GitRepositoryId) + .OnDelete(DeleteBehavior.Cascade); + + // App — add Namespace field constraint. + builder.Entity(entity => + { + entity.Property(a => a.Namespace).HasMaxLength(63); + }); + + // AppQuota — 1:1 with App, cascades on app delete. + builder.Entity(entity => + { + entity.HasKey(q => q.Id); + entity.HasIndex(q => q.AppId).IsUnique(); + entity.Property(q => q.CpuRequest).HasMaxLength(20); + entity.Property(q => q.CpuLimit).HasMaxLength(20); + entity.Property(q => q.MemoryRequest).HasMaxLength(20); + entity.Property(q => q.MemoryLimit).HasMaxLength(20); + + entity.HasOne(q => q.App) + .WithOne(a => a.Quota) + .HasForeignKey(q => q.AppId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // AppNetworkPolicy — many per app, name unique within app. + builder.Entity(entity => + { + entity.HasKey(p => p.Id); + entity.HasIndex(p => new { p.AppId, p.Name }).IsUnique(); + entity.Property(p => p.Name).HasMaxLength(63).IsRequired(); + entity.Property(p => p.PolicyType).HasConversion().HasMaxLength(30); + entity.Property(p => p.AllowFromNamespace).HasMaxLength(63); + + entity.HasOne(p => p.App) + .WithMany(a => a.NetworkPolicies) + .HasForeignKey(p => p.AppId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // AppRbacPolicy — 1:1 with App. + builder.Entity(entity => + { + entity.HasKey(p => p.Id); + entity.HasIndex(p => p.AppId).IsUnique(); + entity.Property(p => p.ServiceAccountName).HasMaxLength(63).IsRequired(); + + entity.HasOne(p => p.App) + .WithOne(a => a.RbacPolicy) + .HasForeignKey(p => p.AppId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // AppRbacRule — many per AppRbacPolicy. + builder.Entity(entity => + { + entity.HasKey(r => r.Id); + entity.Property(r => r.ApiGroups).HasMaxLength(200).IsRequired(); + entity.Property(r => r.Resources).HasMaxLength(500).IsRequired(); + entity.Property(r => r.Verbs).HasMaxLength(200).IsRequired(); + + entity.HasOne(r => r.Policy) + .WithMany(p => p.Rules) + .HasForeignKey(r => r.AppRbacPolicyId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // GitRepository — tenant-scoped, name unique within a tenant. + + builder.Entity(entity => + { + entity.HasKey(r => r.Id); + entity.HasIndex(r => new { r.TenantId, r.Name }).IsUnique(); + entity.Property(r => r.Name).HasMaxLength(200).IsRequired(); + entity.Property(r => r.Url).HasMaxLength(2000).IsRequired(); + entity.Property(r => r.AuthType).HasConversion().HasMaxLength(20); + entity.Property(r => r.Username).HasMaxLength(300); + entity.Property(r => r.DefaultBranch).HasMaxLength(200).HasDefaultValue("main"); + + entity.HasOne(r => r.Tenant) + .WithMany(t => t.GitRepositories) + .HasForeignKey(r => r.TenantId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // GitKnownHost — trusted SSH host fingerprints, unique per (TenantId, Hostname). + + builder.Entity(entity => + { + entity.HasKey(h => h.Id); + entity.HasIndex(h => new { h.TenantId, h.Hostname }).IsUnique(); + entity.Property(h => h.Hostname).HasMaxLength(253).IsRequired(); + entity.Property(h => h.Fingerprint).HasMaxLength(200).IsRequired(); + entity.Property(h => h.KeyType).HasMaxLength(50).IsRequired(); + + entity.HasOne(h => h.Tenant) + .WithMany(t => t.GitKnownHosts) + .HasForeignKey(h => h.TenantId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // AppDeployment — git source FK and parent/child app-of-apps relationship. + + builder.Entity(entity => + { + entity.Property(d => d.GitPath).HasMaxLength(500); + entity.Property(d => d.GitRevision).HasMaxLength(200); + entity.Property(d => d.GitLastSyncedCommit).HasMaxLength(40); + + entity.HasOne(d => d.GitRepository) + .WithMany(r => r.Deployments) + .HasForeignKey(d => d.GitRepositoryId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(d => d.ParentDeployment) + .WithMany(d => d.ChildDeployments) + .HasForeignKey(d => d.ParentDeploymentId) + .OnDelete(DeleteBehavior.Cascade); + }); } } diff --git a/src/EntKube.Web/Data/AuditEvent.cs b/src/EntKube.Web/Data/AuditEvent.cs new file mode 100644 index 0000000..bcecc60 --- /dev/null +++ b/src/EntKube.Web/Data/AuditEvent.cs @@ -0,0 +1,37 @@ +namespace EntKube.Web.Data; + +/// +/// An immutable record of an action taken by a user or the system on a deployment +/// resource. Written on every destructive or significant operation — restart, scale, +/// delete, apply, install — so operators have a full activity timeline. +/// +public class AuditEvent +{ + public Guid Id { get; set; } + + /// The deployment the action was taken on, if applicable. + public Guid? DeploymentId { get; set; } + + /// + /// The identity that performed the action. Contains the user's email/name + /// from the authentication state. Null for system-initiated actions. + /// + public string? PerformedBy { get; set; } + + /// Short verb describing what happened, e.g. "RestartWorkload", "ScalePod", "HelmInstall". + public required string Action { get; set; } + + /// The Kubernetes resource kind the action targeted, e.g. "Deployment", "Pod". + public required string ResourceKind { get; set; } + + /// The Kubernetes resource name that was affected. + public string? ResourceName { get; set; } + + /// Extra context: replica count for scale, chart version for install, etc. + public string? Details { get; set; } + + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; + + // Navigation + public AppDeployment? Deployment { get; set; } +} diff --git a/src/EntKube.Web/Data/CacheBinding.cs b/src/EntKube.Web/Data/CacheBinding.cs new file mode 100644 index 0000000..3e29b2f --- /dev/null +++ b/src/EntKube.Web/Data/CacheBinding.cs @@ -0,0 +1,33 @@ +namespace EntKube.Web.Data; + +/// +/// Links a Redis cluster to an AppDeployment. Many bindings can point to the +/// same cluster — a shared Redis instance can serve multiple apps simultaneously. +/// +/// On sync, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, and REDIS_URL are written +/// as a Kubernetes Secret into the app deployment's namespace so the app can +/// connect without knowing which Redis cluster backs it. +/// +public class CacheBinding +{ + public Guid Id { get; set; } + + public Guid RedisClusterId { get; set; } + + public Guid AppDeploymentId { get; set; } + + public Guid TenantId { get; set; } + + /// Kubernetes Secret name created in the app deployment's namespace. + public required string KubernetesSecretName { get; set; } + + public bool SyncEnabled { get; set; } = true; + + public DateTime? LastSyncedAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public RedisCluster RedisCluster { get; set; } = null!; + public AppDeployment AppDeployment { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/CnpgCluster.cs b/src/EntKube.Web/Data/CnpgCluster.cs index 675bbcc..5b1b2f7 100644 --- a/src/EntKube.Web/Data/CnpgCluster.cs +++ b/src/EntKube.Web/Data/CnpgCluster.cs @@ -83,6 +83,19 @@ public class CnpgCluster /// public int RetentionDays { get; set; } = 30; + /// + /// Maximum number of completed Backup CRs to keep in Kubernetes. When exceeded + /// during a cleanup, the oldest completed backups are deleted from K8s and the DB. + /// Does not affect WAL archiving or Barman's own retention — use RetentionDays for that. + /// + public int MaxBackups { get; set; } = 20; + + /// + /// True when the cluster was registered from an existing CNPG deployment (not provisioned by EntKube). + /// Delete should offer "Unregister" (remove from EntKube only) in addition to full deletion. + /// + public bool IsExternal { get; set; } + /// /// Current lifecycle status of the cluster. /// diff --git a/src/EntKube.Web/Data/DatabaseBinding.cs b/src/EntKube.Web/Data/DatabaseBinding.cs index 326ccbd..46eac61 100644 --- a/src/EntKube.Web/Data/DatabaseBinding.cs +++ b/src/EntKube.Web/Data/DatabaseBinding.cs @@ -26,6 +26,12 @@ public class DatabaseBinding /// public Guid? MongoDatabaseId { get; set; } + /// + /// The registered (non-CNPG) PostgreSQL database whose credentials should be synced. + /// Mutually exclusive with CnpgDatabaseId and MongoDatabaseId. + /// + public Guid? RegisteredPostgresDatabaseId { get; set; } + /// /// The deployment that consumes the database credentials. /// The secret is written into this deployment's namespace on its cluster. @@ -56,5 +62,6 @@ public class DatabaseBinding // Navigation public CnpgDatabase? CnpgDatabase { get; set; } public MongoDatabase? MongoDatabase { get; set; } + public RegisteredPostgresDatabase? RegisteredPostgresDatabase { get; set; } public AppDeployment AppDeployment { get; set; } = null!; } diff --git a/src/EntKube.Web/Data/DeploymentEnums.cs b/src/EntKube.Web/Data/DeploymentEnums.cs index b0a6239..9834bd7 100644 --- a/src/EntKube.Web/Data/DeploymentEnums.cs +++ b/src/EntKube.Web/Data/DeploymentEnums.cs @@ -22,7 +22,26 @@ public enum DeploymentType /// A Helm chart from any repository. The user provides the repo URL, /// chart name, version, and dynamic values. /// - HelmChart + HelmChart, + + /// + /// Raw YAML manifests sourced from a Git repository. The platform fetches + /// the YAML files at the specified path and applies them to the cluster. + /// + GitYaml, + + /// + /// A Helm chart sourced from a Git repository (the chart lives in the repo, + /// not in a chart museum). The chart directory is at GitPath. + /// + GitHelm, + + /// + /// App-of-apps sourced from a Git repository. The platform reads ArgoCD + /// Application CRD YAML files at GitPath and creates or reconciles child + /// AppDeployment rows automatically. + /// + GitAppOfApps } /// diff --git a/src/EntKube.Web/Data/DeploymentHealthSnapshot.cs b/src/EntKube.Web/Data/DeploymentHealthSnapshot.cs new file mode 100644 index 0000000..5146516 --- /dev/null +++ b/src/EntKube.Web/Data/DeploymentHealthSnapshot.cs @@ -0,0 +1,14 @@ +namespace EntKube.Web.Data; + +public class DeploymentHealthSnapshot +{ + public Guid Id { get; set; } + public Guid DeploymentId { get; set; } + public HealthStatus HealthStatus { get; set; } + public SyncStatus SyncStatus { get; set; } + public int? ReadyReplicas { get; set; } + public int? TotalReplicas { get; set; } + public DateTime SnapshotAt { get; set; } = DateTime.UtcNow; + + public AppDeployment Deployment { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/DeploymentManifest.cs b/src/EntKube.Web/Data/DeploymentManifest.cs index 1ba56c5..313fec2 100644 --- a/src/EntKube.Web/Data/DeploymentManifest.cs +++ b/src/EntKube.Web/Data/DeploymentManifest.cs @@ -35,6 +35,12 @@ public class DeploymentManifest /// public required string YamlContent { get; set; } + /// + /// For Git-sourced manifests: the relative file path within the repository that + /// produced this manifest. Null for manually created or pasted manifests. + /// + public string? SourceFile { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; diff --git a/src/EntKube.Web/Data/DockerRegistryCredential.cs b/src/EntKube.Web/Data/DockerRegistryCredential.cs new file mode 100644 index 0000000..d141ed1 --- /dev/null +++ b/src/EntKube.Web/Data/DockerRegistryCredential.cs @@ -0,0 +1,68 @@ +namespace EntKube.Web.Data; + +/// +/// Docker / OCI registry authentication credential stored in the tenant vault. +/// The password is encrypted with the tenant's DEK (AES-256-GCM). When +/// KubernetesClusterId + KubernetesSecretName + KubernetesNamespace are set the +/// credential can be pushed to a cluster as a kubernetes.io/dockerconfigjson +/// typed Secret so workloads can pull private images. +/// +public class DockerRegistryCredential +{ + public Guid Id { get; set; } + + /// + /// The tenant vault that owns this credential. Used to look up the DEK + /// for encryption/decryption. + /// + public Guid VaultId { get; set; } + + /// Human-friendly display name, e.g. "Production ACR" or "Docker Hub pull". + public required string Name { get; set; } + + /// Registry type — drives the server URL default and UI display label. + public DockerRegistryType RegistryType { get; set; } = DockerRegistryType.Generic; + + /// + /// Registry server URL, e.g. "https://index.docker.io/v1/", "myacr.azurecr.io", + /// "registry.example.com". Stored in plain text. + /// + public required string Server { get; set; } + + /// Registry username or service principal ID. Stored in plain text. + public required string Username { get; set; } + + /// + /// Password, personal access token, or service principal secret. + /// Encrypted with the tenant DEK using AES-256-GCM. Combined ciphertext + tag. + /// + public required byte[] EncryptedPassword { get; set; } + + /// GCM nonce used when encrypting EncryptedPassword. Unique per write. + public required byte[] PasswordNonce { get; set; } + + /// Optional contact email. Required by some registries for the auth entry. + public string? Email { get; set; } + + /// + /// If set, this credential is scoped to a specific app. Null means tenant-wide. + /// + public Guid? AppId { get; set; } + + /// Target Kubernetes cluster for the dockerconfigjson secret sync. + public Guid? KubernetesClusterId { get; set; } + + /// Name of the Kubernetes Secret to create/update on sync, e.g. "registry-creds". + public string? KubernetesSecretName { get; set; } + + /// Kubernetes namespace that receives the synced Secret. + public string? KubernetesNamespace { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // Navigation properties + public SecretVault Vault { get; set; } = null!; + public App? App { get; set; } + public KubernetesCluster? KubernetesCluster { get; set; } +} diff --git a/src/EntKube.Web/Data/DockerRegistryType.cs b/src/EntKube.Web/Data/DockerRegistryType.cs new file mode 100644 index 0000000..1ce76b2 --- /dev/null +++ b/src/EntKube.Web/Data/DockerRegistryType.cs @@ -0,0 +1,16 @@ +namespace EntKube.Web.Data; + +/// +/// Well-known OCI / Docker registry providers. Drives server URL defaults and +/// display labels in the UI. Use for any registry not +/// listed here. +/// +public enum DockerRegistryType +{ + Generic, + DockerHub, + AzureContainerRegistry, + Harbor, + Quay, + GitHubContainerRegistry +} diff --git a/src/EntKube.Web/Data/ExternalRoute.cs b/src/EntKube.Web/Data/ExternalRoute.cs index 7ea2b3a..184d0f2 100644 --- a/src/EntKube.Web/Data/ExternalRoute.cs +++ b/src/EntKube.Web/Data/ExternalRoute.cs @@ -83,6 +83,17 @@ public class ExternalRoute public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + // ── Health monitoring ── + + /// When the last automated health check ran against this hostname. + public DateTime? LastHealthCheckAt { get; set; } + + /// HTTP status code from the last health check. Null if the request failed entirely. + public int? LastStatusCode { get; set; } + + /// True when the last health check returned a 2xx or redirect response. + public bool? IsReachable { get; set; } + // Navigation public ClusterComponent Component { get; set; } = null!; } diff --git a/src/EntKube.Web/Data/ExternalRouteHealthHistory.cs b/src/EntKube.Web/Data/ExternalRouteHealthHistory.cs new file mode 100644 index 0000000..e4ca798 --- /dev/null +++ b/src/EntKube.Web/Data/ExternalRouteHealthHistory.cs @@ -0,0 +1,13 @@ +namespace EntKube.Web.Data; + +public class ExternalRouteHealthHistory +{ + public Guid Id { get; set; } + public Guid RouteId { get; set; } + public bool IsReachable { get; set; } + public int? StatusCode { get; set; } + public int? ResponseMs { get; set; } + public DateTime CheckedAt { get; set; } = DateTime.UtcNow; + + public ExternalRoute Route { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/GitAuthType.cs b/src/EntKube.Web/Data/GitAuthType.cs new file mode 100644 index 0000000..50c1dcb --- /dev/null +++ b/src/EntKube.Web/Data/GitAuthType.cs @@ -0,0 +1,9 @@ +namespace EntKube.Web.Data; + +public enum GitAuthType +{ + None, + HttpsPat, + HttpsPassword, + SshKey +} diff --git a/src/EntKube.Web/Data/GitKnownHost.cs b/src/EntKube.Web/Data/GitKnownHost.cs new file mode 100644 index 0000000..ec8bfc8 --- /dev/null +++ b/src/EntKube.Web/Data/GitKnownHost.cs @@ -0,0 +1,37 @@ +namespace EntKube.Web.Data; + +/// +/// A trusted SSH host fingerprint for a tenant. When a GitRepository with SshKey +/// auth connects for the first time, the server's fingerprint is recorded here +/// (auto-trust on first connect, like ArgoCD). Subsequent connections that present +/// a different fingerprint for the same hostname will fail. +/// +/// Scoped to tenant rather than individual repositories so that multiple repos on +/// the same host (e.g. github.com) share one trust entry. +/// +public class GitKnownHost +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// + /// The hostname (e.g. "github.com", "dev.azure.com"). + /// + public required string Hostname { get; set; } + + /// + /// SHA-256 fingerprint of the host key, base64-encoded (e.g. "SHA256:nThbg6..."). + /// + public required string Fingerprint { get; set; } + + /// + /// The key type reported by the server (e.g. "ssh-rsa", "ecdsa-sha2-nistp256", "ssh-ed25519"). + /// + public required string KeyType { get; set; } + + public DateTime TrustedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/GitRepository.cs b/src/EntKube.Web/Data/GitRepository.cs new file mode 100644 index 0000000..0c725de --- /dev/null +++ b/src/EntKube.Web/Data/GitRepository.cs @@ -0,0 +1,52 @@ +namespace EntKube.Web.Data; + +/// +/// A registered Git repository credential set, scoped to a tenant. +/// Acts like ArgoCD's Repository — a reusable auth configuration that multiple +/// deployments can reference. Credentials are stored encrypted in the tenant vault +/// (VaultSecret rows with GitRepositoryId set), never in this table. +/// +/// Supported auth types: +/// - None: public repo, no credentials +/// - HttpsPat: HTTPS with a single PAT (stored as vault secret "PAT") +/// - HttpsPassword: HTTPS with username + password (stored as vault secret "PASSWORD"; username stored here) +/// - SshKey: SSH private key (stored as vault secret "SSH_PRIVATE_KEY") +/// +public class GitRepository +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// + /// Human-friendly label for this repository (e.g. "prod-gitops-repo"). + /// Must be unique within the tenant. + /// + public required string Name { get; set; } + + /// + /// The clone URL. HTTPS (https://...) or SSH (git@github.com:...) depending on AuthType. + /// + public required string Url { get; set; } + + public GitAuthType AuthType { get; set; } = GitAuthType.None; + + /// + /// For HttpsPassword auth: the username to use alongside the vault-stored password. + /// For Azure DevOps PAT auth this is often just the string "x-auth-token" or empty. + /// Not used for SshKey or HttpsPat auth. + /// + public string? Username { get; set; } + + /// + /// The default branch/revision to check out when a deployment does not specify one. + /// + public string DefaultBranch { get; set; } = "main"; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public ICollection Deployments { get; set; } = []; + public ICollection KnownHosts { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/HarborComponentConfig.cs b/src/EntKube.Web/Data/HarborComponentConfig.cs new file mode 100644 index 0000000..b2a5ca8 --- /dev/null +++ b/src/EntKube.Web/Data/HarborComponentConfig.cs @@ -0,0 +1,40 @@ +namespace EntKube.Web.Data; + +/// +/// Harbor-specific configuration for a managed Harbor instance. +/// Tracks the backing CNPG database, S3 storage bucket, admin credentials, and registry URL. +/// +/// Admin password is stored as a component vault secret (key: HARBOR_ADMIN_PASSWORD). +/// S3 credentials are stored as component vault secrets (keys: harbor-s3-access-key, +/// harbor-s3-secret-key) and injected into Helm values at install time. +/// CNPG database password is stored as a component vault secret (key: harbor-db-password). +/// +public class HarborComponentConfig +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// The installed Harbor ClusterComponent this config belongs to. + public Guid ClusterComponentId { get; set; } + + /// Managed CNPG database backing this Harbor instance. Null uses Harbor's built-in Postgres. + public Guid? CnpgDatabaseId { get; set; } + + /// S3-compatible storage link for Harbor artifact storage. Null uses local filesystem PVC. + public Guid? StorageLinkId { get; set; } + + /// Harbor admin username (default "admin"). + public string AdminUsername { get; set; } = "admin"; + + /// Public URL of the Harbor registry (e.g. "https://registry.example.com"). + public string? RegistryUrl { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public ClusterComponent ClusterComponent { get; set; } = null!; + public CnpgDatabase? CnpgDatabase { get; set; } + public StorageLink? StorageLink { get; set; } +} diff --git a/src/EntKube.Web/Data/HarborProject.cs b/src/EntKube.Web/Data/HarborProject.cs new file mode 100644 index 0000000..f0687f4 --- /dev/null +++ b/src/EntKube.Web/Data/HarborProject.cs @@ -0,0 +1,34 @@ +namespace EntKube.Web.Data; + +/// +/// Tracks a Harbor project that EntKube is managing. Used to link a Harbor +/// project to a customer app so the customer can manage it through the portal. +/// +/// The project itself lives in Harbor; this record stores the EntKube-side +/// association (tenant scope, optional app link) so the portal can surface it. +/// +public class HarborProject +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// The Harbor instance this project belongs to. + public Guid HarborComponentConfigId { get; set; } + + /// Harbor project name (used as the path segment in API calls and image references). + public required string ProjectName { get; set; } + + /// + /// When set, this project is linked to a customer app and the customer can + /// manage their project (repositories, robot accounts) through the portal. + /// + public Guid? LinkedAppId { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public HarborComponentConfig HarborComponentConfig { get; set; } = null!; + public App? LinkedApp { get; set; } +} diff --git a/src/EntKube.Web/Data/IncidentNote.cs b/src/EntKube.Web/Data/IncidentNote.cs new file mode 100644 index 0000000..8b45269 --- /dev/null +++ b/src/EntKube.Web/Data/IncidentNote.cs @@ -0,0 +1,12 @@ +namespace EntKube.Web.Data; + +public class IncidentNote +{ + public Guid Id { get; set; } + public Guid IncidentId { get; set; } + public required string Author { get; set; } + public required string Content { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public AlertIncident Incident { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/KeycloakBackup.cs b/src/EntKube.Web/Data/KeycloakBackup.cs index 37a4a3e..5305095 100644 --- a/src/EntKube.Web/Data/KeycloakBackup.cs +++ b/src/EntKube.Web/Data/KeycloakBackup.cs @@ -20,7 +20,7 @@ public class KeycloakBackup public Guid TenantId { get; set; } - public Guid StorageLinkId { get; set; } + public Guid? StorageLinkId { get; set; } /// /// S3 object key (e.g. "keycloak-backups/my-realm/2026-05-19T14-32-00.json"). @@ -45,5 +45,5 @@ public class KeycloakBackup // Navigation public KeycloakRealm Realm { get; set; } = null!; - public StorageLink StorageLink { get; set; } = null!; + public StorageLink? StorageLink { get; set; } } diff --git a/src/EntKube.Web/Data/KeycloakComponentConfig.cs b/src/EntKube.Web/Data/KeycloakComponentConfig.cs index b059515..6af49f6 100644 --- a/src/EntKube.Web/Data/KeycloakComponentConfig.cs +++ b/src/EntKube.Web/Data/KeycloakComponentConfig.cs @@ -1,16 +1,14 @@ namespace EntKube.Web.Data; /// -/// Keycloak-specific configuration attached to an installed Keycloak ClusterComponent. -/// Stores which CNPG database backs this Keycloak instance and the admin credentials -/// needed for the Admin REST API. +/// Keycloak-specific configuration for an instance tracked by EntKube. +/// Covers both EntKube-managed installs (ClusterComponentId set) and externally +/// deployed instances (ClusterComponentId null, DisplayName used instead). /// -/// DB credentials (KC_DB_URL, KC_DB_USERNAME, KC_DB_PASSWORD) are stored as -/// component vault secrets and synced to a Kubernetes Secret so the Helm chart -/// can consume them without any out-of-band secret management. -/// -/// The admin password is stored as a component vault secret named -/// "KEYCLOAK_ADMIN_PASSWORD" and synced to the same Kubernetes Secret. +/// For managed instances: DB credentials and the admin password are stored as +/// component vault secrets (keyed by ClusterComponentId) and synced to K8s. +/// For external instances: only the admin password is stored in vault, keyed by +/// this config's own Id. /// public class KeycloakComponentConfig { @@ -20,16 +18,29 @@ public class KeycloakComponentConfig /// /// The installed Keycloak ClusterComponent this config belongs to. - /// One config per Keycloak component instance. + /// Null for externally deployed Keycloaks not managed by EntKube. /// - public Guid ClusterComponentId { get; set; } + public Guid? ClusterComponentId { get; set; } + + /// + /// Human-readable name for externally deployed Keycloaks (ClusterComponentId is null). + /// Ignored when ClusterComponentId is set — the component name is used instead. + /// + public string? DisplayName { get; set; } /// /// The managed CNPG database backing this Keycloak instance. - /// Null until the operator selects a database. + /// Null when using a registered Postgres database or no database is linked yet. + /// Mutually exclusive with RegisteredPostgresDatabaseId. /// public Guid? CnpgDatabaseId { get; set; } + /// + /// A registered (non-CNPG) PostgreSQL database backing this Keycloak instance. + /// Mutually exclusive with CnpgDatabaseId. + /// + public Guid? RegisteredPostgresDatabaseId { get; set; } + /// /// Keycloak admin username (default "admin"). /// @@ -46,7 +57,8 @@ public class KeycloakComponentConfig // Navigation public Tenant Tenant { get; set; } = null!; - public ClusterComponent ClusterComponent { get; set; } = null!; + public ClusterComponent? ClusterComponent { get; set; } public CnpgDatabase? CnpgDatabase { get; set; } + public RegisteredPostgresDatabase? RegisteredPostgresDatabase { get; set; } public ICollection Realms { get; set; } = []; } diff --git a/src/EntKube.Web/Data/KeycloakRealm.cs b/src/EntKube.Web/Data/KeycloakRealm.cs index 1d9a942..923978e 100644 --- a/src/EntKube.Web/Data/KeycloakRealm.cs +++ b/src/EntKube.Web/Data/KeycloakRealm.cs @@ -42,10 +42,18 @@ public class KeycloakRealm /// public Guid? LinkedAppId { get; set; } + /// + /// Optional named theme (from EntKube's theme library for this Keycloak instance). + /// When set, applying the theme writes its LoginTheme/AccountTheme to Keycloak + /// and associates its CSS overrides with this realm. + /// + public Guid? KeycloakThemeId { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // Navigation public KeycloakComponentConfig ComponentConfig { get; set; } = null!; public App? LinkedApp { get; set; } + public KeycloakTheme? Theme { get; set; } public ICollection Backups { get; set; } = []; } diff --git a/src/EntKube.Web/Data/KeycloakTheme.cs b/src/EntKube.Web/Data/KeycloakTheme.cs new file mode 100644 index 0000000..6610621 --- /dev/null +++ b/src/EntKube.Web/Data/KeycloakTheme.cs @@ -0,0 +1,31 @@ +namespace EntKube.Web.Data; + +/// +/// A named CSS theme bundle for a Keycloak instance. Captures both the +/// Keycloak-native theme to activate (loginTheme / accountTheme) and stores +/// custom CSS overrides in the vault keyed by this theme's Id. Realms can +/// reference a theme to inherit its settings in one operation. +/// +public class KeycloakTheme +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + public Guid KeycloakComponentConfigId { get; set; } + + /// Human-readable name shown in the theme selector (e.g. "Capio Blue"). + public required string Name { get; set; } + + /// Keycloak-native login theme provider name (e.g. "capio", "keycloak"). + public string? LoginTheme { get; set; } + + /// Keycloak-native account console theme provider name. + public string? AccountTheme { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public KeycloakComponentConfig ComponentConfig { get; set; } = null!; + public ICollection Realms { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/MaintenanceWindow.cs b/src/EntKube.Web/Data/MaintenanceWindow.cs new file mode 100644 index 0000000..fc29a98 --- /dev/null +++ b/src/EntKube.Web/Data/MaintenanceWindow.cs @@ -0,0 +1,17 @@ +namespace EntKube.Web.Data; + +public class MaintenanceWindow +{ + public Guid Id { get; set; } + public Guid TenantId { get; set; } + public Guid? ClusterId { get; set; } + public required string Title { get; set; } + public string? Description { get; set; } + public DateTime StartsAt { get; set; } + public DateTime EndsAt { get; set; } + public required string CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public Tenant Tenant { get; set; } = null!; + public KubernetesCluster? Cluster { get; set; } +} diff --git a/src/EntKube.Web/Data/MessagingBinding.cs b/src/EntKube.Web/Data/MessagingBinding.cs new file mode 100644 index 0000000..bf03b2e --- /dev/null +++ b/src/EntKube.Web/Data/MessagingBinding.cs @@ -0,0 +1,45 @@ +namespace EntKube.Web.Data; + +/// +/// Links a RabbitMQ vhost/queue/exchange to an AppDeployment. +/// The platform syncs AMQP connection details (host, port, vhost, credentials, +/// queue/exchange name) into a Kubernetes Secret in the app's namespace, +/// mirroring the DatabaseBinding pattern. +/// +public class MessagingBinding +{ + public Guid Id { get; set; } + + public Guid RabbitMQClusterId { get; set; } + + public Guid AppDeploymentId { get; set; } + + public Guid TenantId { get; set; } + + /// The RabbitMQ virtual host the app connects to. + public required string Vhost { get; set; } + + /// Queue name the app consumes from, if this is a queue binding. + public string? QueueName { get; set; } + + /// Exchange name the app publishes to, if this is an exchange binding. + public string? ExchangeName { get; set; } + + /// + /// Target Kubernetes Secret name in the app deployment's namespace. + /// The secret will contain RABBITMQ_HOST, RABBITMQ_PORT, RABBITMQ_VHOST, + /// RABBITMQ_USERNAME, RABBITMQ_PASSWORD, RABBITMQ_URL, and RABBITMQ_QUEUE + /// or RABBITMQ_EXCHANGE if applicable. + /// + public required string KubernetesSecretName { get; set; } + + public bool SyncEnabled { get; set; } = true; + + public DateTime? LastSyncedAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public RabbitMQCluster Cluster { get; set; } = null!; + public AppDeployment AppDeployment { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.Designer.cs new file mode 100644 index 0000000..1fd0b13 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.Designer.cs @@ -0,0 +1,2224 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260521201858_AddExternalKeycloak")] + partial class AddExternalKeycloak + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.cs new file mode 100644 index 0000000..65e495c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260521201858_AddExternalKeycloak.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddExternalKeycloak : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs", + type: "character varying(200)", + maxLength: 200, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs"); + + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.Designer.cs new file mode 100644 index 0000000..3d15229 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.Designer.cs @@ -0,0 +1,2288 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260522065948_AddKeycloakTheme")] + partial class AddKeycloakTheme + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.cs new file mode 100644 index 0000000..b1160f5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522065948_AddKeycloakTheme.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddKeycloakTheme : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "KeycloakThemes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + KeycloakComponentConfigId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + LoginTheme = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + AccountTheme = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KeycloakThemes", x => x.Id); + table.ForeignKey( + name: "FK_KeycloakThemes_KeycloakComponentConfigs_KeycloakComponentCo~", + column: x => x.KeycloakComponentConfigId, + principalTable: "KeycloakComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakThemes_KeycloakComponentConfigId_Name", + table: "KeycloakThemes", + columns: new[] { "KeycloakComponentConfigId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId", + principalTable: "KeycloakThemes", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropTable( + name: "KeycloakThemes"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.Designer.cs new file mode 100644 index 0000000..e7610a6 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.Designer.cs @@ -0,0 +1,2454 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260522091248_AddRegisteredPostgres")] + partial class AddRegisteredPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.cs new file mode 100644 index 0000000..56b59ae --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522091248_AddRegisteredPostgres.cs @@ -0,0 +1,190 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddRegisteredPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresInstances", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + KubernetesClusterId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Namespace = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + ServiceName = table.Column(type: "character varying(253)", maxLength: 253, nullable: false), + Port = table.Column(type: "integer", nullable: false), + AdminPodName = table.Column(type: "character varying(253)", maxLength: 253, nullable: false), + AdminUsername = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + Notes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresInstances", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_KubernetesClusters_KubernetesCl~", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresDatabases", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RegisteredPostgresInstanceId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + Owner = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + Status = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDatabases", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDatabases_RegisteredPostgresInstances_Reg~", + column: x => x.RegisteredPostgresInstanceId, + principalTable: "RegisteredPostgresInstances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDatabases_RegisteredPostgresInstanceId_Na~", + table: "RegisteredPostgresDatabases", + columns: new[] { "RegisteredPostgresInstanceId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_KubernetesClusterId", + table: "RegisteredPostgresInstances", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_TenantId_Name", + table: "RegisteredPostgresInstances", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPost~", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_Regist~", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgres~", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPost~", + table: "DatabaseBindings"); + + migrationBuilder.DropForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_Regist~", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgres~", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresDatabases"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresInstances"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.Designer.cs new file mode 100644 index 0000000..22a50f2 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.Designer.cs @@ -0,0 +1,2505 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260522110451_AddRegisteredPostgresDump")] + partial class AddRegisteredPostgresDump + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.cs new file mode 100644 index 0000000..40ebf30 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260522110451_AddRegisteredPostgresDump.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddRegisteredPostgresDump : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RegisteredPostgresDumps", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RegisteredPostgresDatabaseId = table.Column(type: "uuid", nullable: false), + StorageLinkId = table.Column(type: "uuid", nullable: false), + S3Key = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + SizeBytes = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDumps", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_RegisteredPostgresDatabases_Registe~", + column: x => x.RegisteredPostgresDatabaseId, + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_RegisteredPostgresDatabaseId", + table: "RegisteredPostgresDumps", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_StorageLinkId", + table: "RegisteredPostgresDumps", + column: "StorageLinkId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RegisteredPostgresDumps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.Designer.cs new file mode 100644 index 0000000..f6fd545 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.Designer.cs @@ -0,0 +1,2517 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260524194104_AddCnpgClusterVaultSecret")] + partial class AddCnpgClusterVaultSecret + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.cs new file mode 100644 index 0000000..5c75d01 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260524194104_AddCnpgClusterVaultSecret.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddCnpgClusterVaultSecret : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CnpgClusterId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId", + principalTable: "CnpgClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "CnpgClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.Designer.cs new file mode 100644 index 0000000..9976e2b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.Designer.cs @@ -0,0 +1,2516 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260525203011_KeycloakBackupStorageLinkNullable")] + partial class KeycloakBackupStorageLinkNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.cs new file mode 100644 index 0000000..ed8bca7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260525203011_KeycloakBackupStorageLinkNullable.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class KeycloakBackupStorageLinkNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.Designer.cs new file mode 100644 index 0000000..f3418e9 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.Designer.cs @@ -0,0 +1,2592 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260526050057_AddHarborComponentConfig")] + partial class AddHarborComponentConfig + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.cs new file mode 100644 index 0000000..b18653c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526050057_AddHarborComponentConfig.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddHarborComponentConfig : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborComponentConfigs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + ClusterComponentId = table.Column(type: "uuid", nullable: false), + CnpgDatabaseId = table.Column(type: "uuid", nullable: true), + StorageLinkId = table.Column(type: "uuid", nullable: true), + AdminUsername = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + RegistryUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborComponentConfigs", x => x.Id); + table.ForeignKey( + name: "FK_HarborComponentConfigs_ClusterComponents_ClusterComponentId", + column: x => x.ClusterComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborComponentConfigs_CnpgDatabases_CnpgDatabaseId", + column: x => x.CnpgDatabaseId, + principalTable: "CnpgDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_ClusterComponentId", + table: "HarborComponentConfigs", + column: "ClusterComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_CnpgDatabaseId", + table: "HarborComponentConfigs", + column: "CnpgDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_StorageLinkId", + table: "HarborComponentConfigs", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_TenantId", + table: "HarborComponentConfigs", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborComponentConfigs"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.Designer.cs new file mode 100644 index 0000000..73f3a89 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.Designer.cs @@ -0,0 +1,2596 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260526113535_AddCnpgMaxBackups")] + partial class AddCnpgMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.cs new file mode 100644 index 0000000..9c4a4da --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526113535_AddCnpgMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddCnpgMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "CnpgClusters", + type: "integer", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..59126d3 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.Designer.cs @@ -0,0 +1,2595 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260526114839_FixCnpgMaxBackupsDefault")] + partial class FixCnpgMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.cs new file mode 100644 index 0000000..c7ab4a9 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526114839_FixCnpgMaxBackupsDefault.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class FixCnpgMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + "UPDATE \"CnpgClusters\" SET \"MaxBackups\" = 20 WHERE \"MaxBackups\" = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.Designer.cs new file mode 100644 index 0000000..5bd9693 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.Designer.cs @@ -0,0 +1,2599 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260526121548_AddMongoMaxBackups")] + partial class AddMongoMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("integer"); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.cs new file mode 100644 index 0000000..5b80302 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526121548_AddMongoMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMongoMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "MongoClusters", + type: "integer", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..4d772d0 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.Designer.cs @@ -0,0 +1,2602 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260526121835_FixMongoMaxBackupsDefault")] + partial class FixMongoMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.cs new file mode 100644 index 0000000..6c99d97 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260526121835_FixMongoMaxBackupsDefault.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class FixMongoMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + "UPDATE \"MongoClusters\" SET \"MaxBackups\" = 20 WHERE \"MaxBackups\" = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.Designer.cs new file mode 100644 index 0000000..f3bafbc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.Designer.cs @@ -0,0 +1,2697 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260527143412_AddDockerRegistryCredential")] + partial class AddDockerRegistryCredential + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.cs new file mode 100644 index 0000000..87b7ad7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260527143412_AddDockerRegistryCredential.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddDockerRegistryCredential : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DockerRegistryCredentials", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + VaultId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + RegistryType = table.Column(type: "integer", nullable: false), + Server = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Username = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + EncryptedPassword = table.Column(type: "bytea", nullable: false), + PasswordNonce = table.Column(type: "bytea", nullable: false), + Email = table.Column(type: "character varying(254)", maxLength: 254, nullable: true), + AppId = table.Column(type: "uuid", nullable: true), + KubernetesClusterId = table.Column(type: "uuid", nullable: true), + KubernetesSecretName = table.Column(type: "character varying(253)", maxLength: 253, nullable: true), + KubernetesNamespace = table.Column(type: "character varying(63)", maxLength: 63, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DockerRegistryCredentials", x => x.Id); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_KubernetesClusters_KubernetesClus~", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_SecretVaults_VaultId", + column: x => x.VaultId, + principalTable: "SecretVaults", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_AppId", + table: "DockerRegistryCredentials", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_KubernetesClusterId", + table: "DockerRegistryCredentials", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_VaultId", + table: "DockerRegistryCredentials", + column: "VaultId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DockerRegistryCredentials"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.Designer.cs new file mode 100644 index 0000000..cb057e9 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.Designer.cs @@ -0,0 +1,2709 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260527151548_AddAppSecretClusterId")] + partial class AddAppSecretClusterId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.cs new file mode 100644 index 0000000..4121e57 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260527151548_AddAppSecretClusterId.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddAppSecretClusterId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KubernetesClusterId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId", + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "KubernetesClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.Designer.cs new file mode 100644 index 0000000..709594b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.Designer.cs @@ -0,0 +1,2771 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260528095223_AddAuditEventsAndRouteHealth")] + partial class AddAuditEventsAndRouteHealth + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.cs new file mode 100644 index 0000000..09a51a6 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260528095223_AddAuditEventsAndRouteHealth.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddAuditEventsAndRouteHealth : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsReachable", + table: "ExternalRoutes", + type: "boolean", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastStatusCode", + table: "ExternalRoutes", + type: "integer", + nullable: true); + + migrationBuilder.CreateTable( + name: "AuditEvents", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + DeploymentId = table.Column(type: "uuid", nullable: true), + PerformedBy = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Action = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + ResourceKind = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + ResourceName = table.Column(type: "character varying(253)", maxLength: 253, nullable: true), + Details = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + OccurredAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuditEvents", x => x.Id); + table.ForeignKey( + name: "FK_AuditEvents_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_DeploymentId", + table: "AuditEvents", + column: "DeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_OccurredAt", + table: "AuditEvents", + column: "OccurredAt"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuditEvents"); + + migrationBuilder.DropColumn( + name: "IsReachable", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastStatusCode", + table: "ExternalRoutes"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.Designer.cs new file mode 100644 index 0000000..1f82a71 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.Designer.cs @@ -0,0 +1,2832 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601071037_AddHarborProject")] + partial class AddHarborProject + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.cs new file mode 100644 index 0000000..d875a9f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601071037_AddHarborProject.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddHarborProject : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborProjects", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + HarborComponentConfigId = table.Column(type: "uuid", nullable: false), + ProjectName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + LinkedAppId = table.Column(type: "uuid", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborProjects", x => x.Id); + table.ForeignKey( + name: "FK_HarborProjects_Apps_LinkedAppId", + column: x => x.LinkedAppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborProjects_HarborComponentConfigs_HarborComponentConfig~", + column: x => x.HarborComponentConfigId, + principalTable: "HarborComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborProjects_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_HarborComponentConfigId_ProjectName", + table: "HarborProjects", + columns: new[] { "HarborComponentConfigId", "ProjectName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_LinkedAppId", + table: "HarborProjects", + column: "LinkedAppId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_TenantId", + table: "HarborProjects", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborProjects"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.Designer.cs new file mode 100644 index 0000000..5f134f7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.Designer.cs @@ -0,0 +1,3002 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601124715_AddRabbitMQ")] + partial class AddRabbitMQ + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.cs new file mode 100644 index 0000000..9317e6b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601124715_AddRabbitMQ.cs @@ -0,0 +1,127 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddRabbitMQ : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RabbitMQClusters", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + KubernetesClusterId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + Namespace = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + RabbitMQVersion = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Replicas = table.Column(type: "integer", nullable: false), + StorageSize = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "character varying(63)", maxLength: 63, nullable: true), + StorageLinkId = table.Column(type: "uuid", nullable: true), + BackupSchedule = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + MaxBackups = table.Column(type: "integer", nullable: false, defaultValue: 10), + Status = table.Column(type: "integer", nullable: false), + LastError = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQClusters", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQClusters_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_RabbitMQClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RabbitMQBackups", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RabbitMQClusterId = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + StorageLinkId = table.Column(type: "uuid", nullable: true), + ObjectKey = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + ClusterName = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + SizeBytes = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "integer", nullable: false), + LastError = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CompletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQBackups", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQBackups_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQBackups_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_RabbitMQClusterId", + table: "RabbitMQBackups", + column: "RabbitMQClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_StorageLinkId", + table: "RabbitMQBackups", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_KubernetesClusterId_Name_Namespace", + table: "RabbitMQClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_StorageLinkId", + table: "RabbitMQClusters", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_TenantId", + table: "RabbitMQClusters", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RabbitMQBackups"); + + migrationBuilder.DropTable( + name: "RabbitMQClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.Designer.cs new file mode 100644 index 0000000..f0af40e --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.Designer.cs @@ -0,0 +1,3014 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601132337_AddRabbitMQCredentialVaultSync")] + partial class AddRabbitMQCredentialVaultSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.cs new file mode 100644 index 0000000..76dde81 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601132337_AddRabbitMQCredentialVaultSync.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddRabbitMQCredentialVaultSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId", + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.Designer.cs new file mode 100644 index 0000000..5bf9611 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.Designer.cs @@ -0,0 +1,3017 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601160417_AddMongoClusterIsExternal")] + partial class AddMongoClusterIsExternal + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.cs new file mode 100644 index 0000000..fbb704d --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601160417_AddMongoClusterIsExternal.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMongoClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "MongoClusters", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.Designer.cs new file mode 100644 index 0000000..0bfddc8 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.Designer.cs @@ -0,0 +1,3309 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601163353_AddMonitoringAndAlerting")] + partial class AddMonitoringAndAlerting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.cs new file mode 100644 index 0000000..67a6e12 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601163353_AddMonitoringAndAlerting.cs @@ -0,0 +1,205 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMonitoringAndAlerting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AlertIncidents", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ClusterId = table.Column(type: "uuid", nullable: false), + Fingerprint = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + AlertName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Severity = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Summary = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + LabelsJson = table.Column(type: "text", nullable: false), + StartsAt = table.Column(type: "timestamp with time zone", nullable: false), + EndsAt = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + AcknowledgedBy = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + AcknowledgedAt = table.Column(type: "timestamp with time zone", nullable: true), + ResolvedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AlertIncidents", x => x.Id); + table.ForeignKey( + name: "FK_AlertIncidents_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DeploymentHealthSnapshots", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + DeploymentId = table.Column(type: "uuid", nullable: false), + HealthStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + SyncStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ReadyReplicas = table.Column(type: "integer", nullable: true), + TotalReplicas = table.Column(type: "integer", nullable: true), + SnapshotAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeploymentHealthSnapshots", x => x.Id); + table.ForeignKey( + name: "FK_DeploymentHealthSnapshots_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationChannels", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Type = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ConfigurationJson = table.Column(type: "text", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false), + SeverityFilter = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationChannels", x => x.Id); + table.ForeignKey( + name: "FK_NotificationChannels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IncidentNotes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + IncidentId = table.Column(type: "uuid", nullable: false), + Author = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Content = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IncidentNotes", x => x.Id); + table.ForeignKey( + name: "FK_IncidentNotes_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationDeliveries", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + IncidentId = table.Column(type: "uuid", nullable: false), + ChannelId = table.Column(type: "uuid", nullable: false), + IsFiring = table.Column(type: "boolean", nullable: false), + SentAt = table.Column(type: "timestamp with time zone", nullable: false), + Success = table.Column(type: "boolean", nullable: false), + Error = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationDeliveries", x => x.Id); + table.ForeignKey( + name: "FK_NotificationDeliveries_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_NotificationDeliveries_NotificationChannels_ChannelId", + column: x => x.ChannelId, + principalTable: "NotificationChannels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_ClusterId_Fingerprint", + table: "AlertIncidents", + columns: new[] { "ClusterId", "Fingerprint" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_StartsAt", + table: "AlertIncidents", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_Status", + table: "AlertIncidents", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_DeploymentHealthSnapshots_DeploymentId_SnapshotAt", + table: "DeploymentHealthSnapshots", + columns: new[] { "DeploymentId", "SnapshotAt" }); + + migrationBuilder.CreateIndex( + name: "IX_IncidentNotes_IncidentId", + table: "IncidentNotes", + column: "IncidentId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationChannels_TenantId_Name", + table: "NotificationChannels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_ChannelId", + table: "NotificationDeliveries", + column: "ChannelId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_IncidentId_ChannelId_IsFiring", + table: "NotificationDeliveries", + columns: new[] { "IncidentId", "ChannelId", "IsFiring" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeploymentHealthSnapshots"); + + migrationBuilder.DropTable( + name: "IncidentNotes"); + + migrationBuilder.DropTable( + name: "NotificationDeliveries"); + + migrationBuilder.DropTable( + name: "AlertIncidents"); + + migrationBuilder.DropTable( + name: "NotificationChannels"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs new file mode 100644 index 0000000..de003c5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs @@ -0,0 +1,3473 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory")] + partial class AddMaintenanceWindowsSlaTargetsRouteHistory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.cs new file mode 100644 index 0000000..c04907c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601181714_AddMaintenanceWindowsSlaTargetsRouteHistory.cs @@ -0,0 +1,152 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMaintenanceWindowsSlaTargetsRouteHistory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ExternalRouteHealthHistories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RouteId = table.Column(type: "uuid", nullable: false), + IsReachable = table.Column(type: "boolean", nullable: false), + StatusCode = table.Column(type: "integer", nullable: true), + ResponseMs = table.Column(type: "integer", nullable: true), + CheckedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExternalRouteHealthHistories", x => x.Id); + table.ForeignKey( + name: "FK_ExternalRouteHealthHistories_ExternalRoutes_RouteId", + column: x => x.RouteId, + principalTable: "ExternalRoutes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MaintenanceWindows", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + ClusterId = table.Column(type: "uuid", nullable: true), + Title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + StartsAt = table.Column(type: "timestamp with time zone", nullable: false), + EndsAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceWindows", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceWindows_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_MaintenanceWindows_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SlaTargets", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: true), + AppId = table.Column(type: "uuid", nullable: true), + TargetPercent = table.Column(type: "double precision", nullable: false), + MeasurementWindowDays = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SlaTargets", x => x.Id); + table.ForeignKey( + name: "FK_SlaTargets_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExternalRouteHealthHistories_RouteId_CheckedAt", + table: "ExternalRouteHealthHistories", + columns: new[] { "RouteId", "CheckedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_ClusterId", + table: "MaintenanceWindows", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_StartsAt", + table: "MaintenanceWindows", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_TenantId_StartsAt", + table: "MaintenanceWindows", + columns: new[] { "TenantId", "StartsAt" }); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_AppId", + table: "SlaTargets", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_CustomerId", + table: "SlaTargets", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_TenantId_CustomerId_AppId", + table: "SlaTargets", + columns: new[] { "TenantId", "CustomerId", "AppId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ExternalRouteHealthHistories"); + + migrationBuilder.DropTable( + name: "MaintenanceWindows"); + + migrationBuilder.DropTable( + name: "SlaTargets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.Designer.cs new file mode 100644 index 0000000..99a5ac1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.Designer.cs @@ -0,0 +1,3543 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601190005_AddMessagingBinding")] + partial class AddMessagingBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.cs new file mode 100644 index 0000000..35b6aef --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601190005_AddMessagingBinding.cs @@ -0,0 +1,65 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMessagingBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MessagingBindings", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RabbitMQClusterId = table.Column(type: "uuid", nullable: false), + AppDeploymentId = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + Vhost = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + QueueName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + ExchangeName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + KubernetesSecretName = table.Column(type: "character varying(253)", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "boolean", nullable: false), + LastSyncedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MessagingBindings", x => x.Id); + table.ForeignKey( + name: "FK_MessagingBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MessagingBindings_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_AppDeploymentId", + table: "MessagingBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_RabbitMQClusterId", + table: "MessagingBindings", + column: "RabbitMQClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MessagingBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.Designer.cs new file mode 100644 index 0000000..c494710 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.Designer.cs @@ -0,0 +1,3546 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260601200000_AddMongoClusterExternalUri")] + partial class AddMongoClusterExternalUri + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.cs new file mode 100644 index 0000000..575b84c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260601200000_AddMongoClusterExternalUri.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddMongoClusterExternalUri : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExternalUri", + table: "MongoClusters", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExternalUri", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.Designer.cs new file mode 100644 index 0000000..c003cca --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.Designer.cs @@ -0,0 +1,3773 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260602091234_AddVpnTunnels")] + partial class AddVpnTunnels + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.cs new file mode 100644 index 0000000..c666093 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602091234_AddVpnTunnels.cs @@ -0,0 +1,176 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddVpnTunnels : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "VpnTunnels", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "text", nullable: true), + TunnelType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + IkeVersion = table.Column(type: "integer", nullable: false), + IkeProposal = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + EspProposal = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DpdDelay = table.Column(type: "integer", nullable: false), + DpdTimeout = table.Column(type: "integer", nullable: false), + LastStatusCheckAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnTunnels", x => x.Id); + table.ForeignKey( + name: "FK_VpnTunnels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnLocalEndpoints", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + VpnTunnelId = table.Column(type: "uuid", nullable: false), + ClusterId = table.Column(type: "uuid", nullable: false), + ComponentId = table.Column(type: "uuid", nullable: true), + Subnets = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + PublicIp = table.Column(type: "character varying(45)", maxLength: 45, nullable: true), + Role = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnLocalEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_ClusterComponents_ComponentId", + column: x => x.ComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnRemoteEndpoints", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + VpnTunnelId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + PublicIp = table.Column(type: "character varying(45)", maxLength: 45, nullable: false), + Subnets = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + AuthMode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + LastSeenAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnRemoteEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnRemoteEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ClusterId", + table: "VpnLocalEndpoints", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ComponentId", + table: "VpnLocalEndpoints", + column: "ComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_VpnTunnelId", + table: "VpnLocalEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnRemoteEndpoints_VpnTunnelId", + table: "VpnRemoteEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnTunnels_TenantId_Name", + table: "VpnTunnels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId", + principalTable: "VpnRemoteEndpoints", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "VpnLocalEndpoints"); + + migrationBuilder.DropTable( + name: "VpnRemoteEndpoints"); + + migrationBuilder.DropTable( + name: "VpnTunnels"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.Designer.cs new file mode 100644 index 0000000..b0ffa5c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.Designer.cs @@ -0,0 +1,3787 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260602103450_AddVpnLifetimeAndIdentity")] + partial class AddVpnLifetimeAndIdentity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.cs new file mode 100644 index 0000000..580ce43 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602103450_AddVpnLifetimeAndIdentity.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddVpnLifetimeAndIdentity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ChildLifetime", + table: "VpnTunnels", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "IkeLifetime", + table: "VpnTunnels", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "LocalId", + table: "VpnRemoteEndpoints", + type: "character varying(500)", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints", + type: "character varying(500)", + maxLength: 500, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ChildLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "IkeLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "LocalId", + table: "VpnRemoteEndpoints"); + + migrationBuilder.DropColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.Designer.cs new file mode 100644 index 0000000..d51cbfd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.Designer.cs @@ -0,0 +1,3879 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260602124608_AddRedisCluster")] + partial class AddRedisCluster + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterSize") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("boolean"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.cs new file mode 100644 index 0000000..ffaac22 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602124608_AddRedisCluster.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddRedisCluster : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RedisClusterId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "RedisClusters", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + KubernetesClusterId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + Namespace = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + ClusterSize = table.Column(type: "integer", nullable: false), + RedisVersion = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + StorageSize = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "character varying(63)", maxLength: 63, nullable: true), + PersistenceEnabled = table.Column(type: "boolean", nullable: false), + Status = table.Column(type: "integer", nullable: false), + LastError = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RedisClusters", x => x.Id); + table.ForeignKey( + name: "FK_RedisClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RedisClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_KubernetesClusterId_Name_Namespace", + table: "RedisClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_TenantId", + table: "RedisClusters", + column: "TenantId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId", + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RedisClusters"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RedisClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.Designer.cs new file mode 100644 index 0000000..b99adfd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.Designer.cs @@ -0,0 +1,3938 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260602185544_AddCacheBinding")] + partial class AddCacheBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterSize") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("boolean"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.cs new file mode 100644 index 0000000..84257ed --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602185544_AddCacheBinding.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddCacheBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CacheBindings", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RedisClusterId = table.Column(type: "uuid", nullable: false), + AppDeploymentId = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + KubernetesSecretName = table.Column(type: "character varying(253)", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "boolean", nullable: false), + LastSyncedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CacheBindings", x => x.Id); + table.ForeignKey( + name: "FK_CacheBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CacheBindings_RedisClusters_RedisClusterId", + column: x => x.RedisClusterId, + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_AppDeploymentId", + table: "CacheBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_RedisClusterId", + table: "CacheBindings", + column: "RedisClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CacheBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260602200000_AddMongoClusterResources.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260602200000_AddMongoClusterResources.cs new file mode 100644 index 0000000..d220c4b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260602200000_AddMongoClusterResources.cs @@ -0,0 +1,51 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260602200000_AddMongoClusterResources")] + public partial class AddMongoClusterResources : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CpuRequest", + table: "MongoClusters", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "CpuLimit", + table: "MongoClusters", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryRequest", + table: "MongoClusters", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryLimit", + table: "MongoClusters", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "CpuRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "CpuLimit", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryLimit", table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260603080000_AddCnpgClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260603080000_AddCnpgClusterIsExternal.cs new file mode 100644 index 0000000..cb4c10b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260603080000_AddCnpgClusterIsExternal.cs @@ -0,0 +1,33 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260603080000_AddCnpgClusterIsExternal")] + public partial class AddCnpgClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "CnpgClusters", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.Designer.cs new file mode 100644 index 0000000..7b1bd4b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.Designer.cs @@ -0,0 +1,4135 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260603215323_AddGitSync")] + partial class AddGitSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("GitAutoSync") + .HasColumnType("boolean"); + + b.Property("GitLastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentDeploymentId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("SourceFile") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TrustedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CpuLimit") + .HasColumnType("text"); + + b.Property("CpuRequest") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MemoryLimit") + .HasColumnType("text"); + + b.Property("MemoryRequest") + .HasColumnType("text"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterSize") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("boolean"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.cs new file mode 100644 index 0000000..29d7ec3 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260603215323_AddGitSync.cs @@ -0,0 +1,251 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddGitSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "VaultSecrets", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "SourceFile", + table: "DeploymentManifests", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitAutoSync", + table: "AppDeployments", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "GitLastSyncedAt", + table: "AppDeployments", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments", + type: "character varying(40)", + maxLength: 40, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitPath", + table: "AppDeployments", + type: "character varying(500)", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "AppDeployments", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRevision", + table: "AppDeployments", + type: "character varying(200)", + maxLength: 200, + nullable: true); + + migrationBuilder.AddColumn( + name: "ParentDeploymentId", + table: "AppDeployments", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "GitRepositories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Url = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + AuthType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Username = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + DefaultBranch = table.Column(type: "character varying(200)", maxLength: 200, nullable: false, defaultValue: "main"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GitRepositories", x => x.Id); + table.ForeignKey( + name: "FK_GitRepositories_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GitKnownHosts", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + Hostname = table.Column(type: "character varying(253)", maxLength: 253, nullable: false), + Fingerprint = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + KeyType = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + TrustedAt = table.Column(type: "timestamp with time zone", nullable: false), + GitRepositoryId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GitKnownHosts", x => x.Id); + table.ForeignKey( + name: "FK_GitKnownHosts_GitRepositories_GitRepositoryId", + column: x => x.GitRepositoryId, + principalTable: "GitRepositories", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_GitKnownHosts_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_GitRepositoryId", + table: "GitKnownHosts", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_TenantId_Hostname", + table: "GitKnownHosts", + columns: new[] { "TenantId", "Hostname" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GitRepositories_TenantId_Name", + table: "GitRepositories", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId", + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "GitKnownHosts"); + + migrationBuilder.DropTable( + name: "GitRepositories"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "SourceFile", + table: "DeploymentManifests"); + + migrationBuilder.DropColumn( + name: "GitAutoSync", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedAt", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitPath", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRevision", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "ParentDeploymentId", + table: "AppDeployments"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.Designer.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.Designer.cs new file mode 100644 index 0000000..cd9ae08 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.Designer.cs @@ -0,0 +1,4326 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + [DbContext(typeof(PostgresApplicationDbContext))] + [Migration("20260604060617_AddAppGovernance")] + partial class AddAppGovernance + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("GitAutoSync") + .HasColumnType("boolean"); + + b.Property("GitLastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentDeploymentId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CustomYaml") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("MaxPods") + .HasColumnType("integer"); + + b.Property("MaxPvcs") + .HasColumnType("integer"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("AutoMountToken") + .HasColumnType("boolean"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AppRbacPolicyId") + .HasColumnType("uuid"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HelmChartName") + .HasColumnType("text"); + + b.Property("HelmChartVersion") + .HasColumnType("text"); + + b.Property("HelmRepoUrl") + .HasColumnType("text"); + + b.Property("HelmValues") + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .HasColumnType("text"); + + b.Property("ReleaseName") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Instances") + .HasColumnType("integer"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("SourceFile") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uuid"); + + b.Property("StatusMessage") + .HasColumnType("text"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ServicePort") + .HasColumnType("integer"); + + b.Property("TlsCertificate") + .HasColumnType("text"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TrustedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakRealmId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasColumnType("text"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContextName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Kubeconfig") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CpuLimit") + .HasColumnType("text"); + + b.Property("CpuRequest") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("integer"); + + b.Property("MemoryLimit") + .HasColumnType("text"); + + b.Property("MemoryRequest") + .HasColumnType("text"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RetentionDays") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectDomainName") + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("ProjectName") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserDomainName") + .HasColumnType("text"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterSize") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("boolean"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BucketName") + .HasColumnType("text"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .HasColumnType("text"); + + b.Property("EnvironmentId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uuid"); + + b.Property("MongoDatabaseId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uuid"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("SyncToKubernetes") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.cs b/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.cs new file mode 100644 index 0000000..f4e8054 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Postgres/20260604060617_AddAppGovernance.cs @@ -0,0 +1,153 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Postgres +{ + /// + public partial class AddAppGovernance : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Namespace", + table: "Apps", + type: "character varying(63)", + maxLength: 63, + nullable: true); + + migrationBuilder.CreateTable( + name: "AppNetworkPolicies", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AppId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + PolicyType = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + AllowFromNamespace = table.Column(type: "character varying(63)", maxLength: 63, nullable: true), + CustomYaml = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AppNetworkPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppNetworkPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppQuotas", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AppId = table.Column(type: "uuid", nullable: false), + CpuRequest = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + CpuLimit = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + MemoryRequest = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + MemoryLimit = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + MaxPods = table.Column(type: "integer", nullable: true), + MaxPvcs = table.Column(type: "integer", nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppQuotas", x => x.Id); + table.ForeignKey( + name: "FK_AppQuotas_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacPolicies", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AppId = table.Column(type: "uuid", nullable: false), + ServiceAccountName = table.Column(type: "character varying(63)", maxLength: 63, nullable: false), + AutoMountToken = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacRules", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AppRbacPolicyId = table.Column(type: "uuid", nullable: false), + ApiGroups = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Resources = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Verbs = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacRules", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacRules_AppRbacPolicies_AppRbacPolicyId", + column: x => x.AppRbacPolicyId, + principalTable: "AppRbacPolicies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AppNetworkPolicies_AppId_Name", + table: "AppNetworkPolicies", + columns: new[] { "AppId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppQuotas_AppId", + table: "AppQuotas", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacPolicies_AppId", + table: "AppRbacPolicies", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacRules_AppRbacPolicyId", + table: "AppRbacRules", + column: "AppRbacPolicyId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AppNetworkPolicies"); + + migrationBuilder.DropTable( + name: "AppQuotas"); + + migrationBuilder.DropTable( + name: "AppRbacRules"); + + migrationBuilder.DropTable( + name: "AppRbacPolicies"); + + migrationBuilder.DropColumn( + name: "Namespace", + table: "Apps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Postgres/PostgresApplicationDbContextModelSnapshot.cs b/src/EntKube.Web/Data/Migrations/Postgres/PostgresApplicationDbContextModelSnapshot.cs index 6020637..2f4e174 100644 --- a/src/EntKube.Web/Data/Migrations/Postgres/PostgresApplicationDbContextModelSnapshot.cs +++ b/src/EntKube.Web/Data/Migrations/Postgres/PostgresApplicationDbContextModelSnapshot.cs @@ -22,6 +22,83 @@ namespace EntKube.Web.Data.Migrations.Postgres NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Property("Id") @@ -39,6 +116,10 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + b.HasKey("Id"); b.HasIndex("CustomerId", "Name") @@ -65,6 +146,27 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("EnvironmentId") .HasColumnType("uuid"); + b.Property("GitAutoSync") + .HasColumnType("boolean"); + + b.Property("GitLastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + b.Property("HealthStatus") .IsRequired() .HasMaxLength(20) @@ -98,6 +200,9 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasMaxLength(63) .HasColumnType("character varying(63)"); + b.Property("ParentDeploymentId") + .HasColumnType("uuid"); + b.Property("StatusMessage") .HasColumnType("text"); @@ -117,6 +222,10 @@ namespace EntKube.Web.Data.Migrations.Postgres b.HasIndex("EnvironmentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + b.HasIndex("AppId", "Name") .IsUnique(); @@ -141,6 +250,138 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("AppEnvironments"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CustomYaml") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("MaxPods") + .HasColumnType("integer"); + + b.Property("MaxPvcs") + .HasColumnType("integer"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("AutoMountToken") + .HasColumnType("boolean"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AppRbacPolicyId") + .HasColumnType("uuid"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => { b.Property("Id") @@ -205,6 +446,87 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("AspNetUsers", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Property("Id") @@ -321,12 +643,20 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("Instances") .HasColumnType("integer"); + b.Property("IsExternal") + .HasColumnType("boolean"); + b.Property("KubernetesClusterId") .HasColumnType("uuid"); b.Property("LastError") .HasColumnType("text"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + b.Property("Name") .IsRequired() .HasMaxLength(63) @@ -478,6 +808,9 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("MongoDatabaseId") .HasColumnType("uuid"); + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + b.Property("SyncEnabled") .HasColumnType("boolean"); @@ -489,9 +822,46 @@ namespace EntKube.Web.Data.Migrations.Postgres b.HasIndex("MongoDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.ToTable("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeploymentId") + .HasColumnType("uuid"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("integer"); + + b.Property("SnapshotAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TotalReplicas") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => { b.Property("Id") @@ -517,6 +887,9 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("SortOrder") .HasColumnType("integer"); + b.Property("SourceFile") + .HasColumnType("text"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -595,6 +968,76 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("DeploymentResources"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("character varying(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RegistryType") + .HasColumnType("integer"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("VaultId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.Property("Id") @@ -649,6 +1092,15 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasMaxLength(253) .HasColumnType("character varying(253)"); + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("LastHealthCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStatusCode") + .HasColumnType("integer"); + b.Property("PathPrefix") .IsRequired() .HasMaxLength(200) @@ -679,6 +1131,120 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("ExternalRoutes"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReachable") + .HasColumnType("boolean"); + + b.Property("ResponseMs") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("uuid"); + + b.Property("StatusCode") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TrustedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Property("Id") @@ -722,6 +1288,113 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("GroupMemberships"); }); + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uuid"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LinkedAppId") + .HasColumnType("uuid"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => { b.Property("Id") @@ -756,7 +1429,7 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("Status") .HasColumnType("integer"); - b.Property("StorageLinkId") + b.Property("StorageLinkId") .HasColumnType("uuid"); b.Property("TenantId") @@ -786,7 +1459,7 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasMaxLength(100) .HasColumnType("character varying(100)"); - b.Property("ClusterComponentId") + b.Property("ClusterComponentId") .HasColumnType("uuid"); b.Property("CnpgDatabaseId") @@ -795,6 +1468,13 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + b.Property("TenantId") .HasColumnType("uuid"); @@ -804,6 +1484,8 @@ namespace EntKube.Web.Data.Migrations.Postgres b.HasIndex("CnpgDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("TenantId"); b.ToTable("KeycloakComponentConfigs"); @@ -832,6 +1514,9 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("KeycloakComponentConfigId") .HasColumnType("uuid"); + b.Property("KeycloakThemeId") + .HasColumnType("uuid"); + b.Property("LinkedAppId") .HasColumnType("uuid"); @@ -848,6 +1533,8 @@ namespace EntKube.Web.Data.Migrations.Postgres b.HasKey("Id"); + b.HasIndex("KeycloakThemeId"); + b.HasIndex("LinkedAppId"); b.HasIndex("KeycloakComponentConfigId", "RealmName") @@ -856,6 +1543,42 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("KeycloakRealms"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uuid"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Property("Id") @@ -897,6 +1620,103 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppDeploymentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SyncEnabled") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.Property("Id") @@ -944,18 +1764,41 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasMaxLength(100) .HasColumnType("character varying(100)"); + b.Property("CpuLimit") + .HasColumnType("text"); + + b.Property("CpuRequest") + .HasColumnType("text"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("ExternalUri") + .HasColumnType("text"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + b.Property("KubernetesClusterId") .HasColumnType("uuid"); b.Property("LastError") .HasColumnType("text"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + b.Property("Members") .HasColumnType("integer"); + b.Property("MemoryLimit") + .HasColumnType("text"); + + b.Property("MemoryRequest") + .HasColumnType("text"); + b.Property("MongoVersion") .IsRequired() .HasMaxLength(20) @@ -1032,6 +1875,82 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("MongoDatabases"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IncidentId") + .HasColumnType("uuid"); + + b.Property("IsFiring") + .HasColumnType("boolean"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Property("Id") @@ -1077,6 +1996,310 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("OpenStackConnections"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Replicas") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterSize") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("boolean"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("character varying(253)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Property("Id") @@ -1105,6 +2328,42 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("SecretVaults"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("MeasurementWindowDays") + .HasColumnType("integer"); + + b.Property("TargetPercent") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.Property("Id") @@ -1279,6 +2538,9 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("AppId") .HasColumnType("uuid"); + b.Property("CnpgClusterId") + .HasColumnType("uuid"); + b.Property("CnpgDatabaseId") .HasColumnType("uuid"); @@ -1292,6 +2554,12 @@ namespace EntKube.Web.Data.Migrations.Postgres .IsRequired() .HasColumnType("bytea"); + b.Property("GitRepositoryId") + .HasColumnType("uuid"); + + b.Property("KubernetesClusterId") + .HasColumnType("uuid"); + b.Property("KubernetesNamespace") .HasMaxLength(63) .HasColumnType("character varying(63)"); @@ -1318,6 +2586,15 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("OpenStackConnectionId") .HasColumnType("uuid"); + b.Property("RabbitMQClusterId") + .HasColumnType("uuid"); + + b.Property("RedisClusterId") + .HasColumnType("uuid"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uuid"); + b.Property("StorageLinkId") .HasColumnType("uuid"); @@ -1330,20 +2607,37 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Property("VaultId") .HasColumnType("uuid"); + b.Property("VpnRemoteEndpointId") + .HasColumnType("uuid"); + b.HasKey("Id"); b.HasIndex("AppId"); + b.HasIndex("CnpgClusterId"); + b.HasIndex("CnpgDatabaseId"); b.HasIndex("ComponentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + b.HasIndex("MongoClusterId"); b.HasIndex("MongoDatabaseId"); + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("StorageLinkId"); + b.HasIndex("VpnRemoteEndpointId"); + b.HasIndex("VaultId", "AppId", "Name") .IsUnique(); @@ -1353,6 +2647,175 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("VaultSecrets"); }); + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClusterId") + .HasColumnType("uuid"); + + b.Property("ComponentId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildLifetime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DpdDelay") + .HasColumnType("integer"); + + b.Property("DpdTimeout") + .HasColumnType("integer"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeLifetime") + .HasColumnType("integer"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IkeVersion") + .HasColumnType("integer"); + + b.Property("LastStatusCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -1485,6 +2948,17 @@ namespace EntKube.Web.Data.Migrations.Postgres b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.HasOne("EntKube.Web.Data.Customer", "Customer") @@ -1516,11 +2990,25 @@ namespace EntKube.Web.Data.Migrations.Postgres .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); b.Navigation("Cluster"); b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); }); modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => @@ -1542,6 +3030,79 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Environment"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") @@ -1649,11 +3210,29 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("AppDeployment"); b.Navigation("CnpgDatabase"); b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); }); modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => @@ -1685,6 +3264,31 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("ParentResource"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1707,6 +3311,43 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Component"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1737,26 +3378,7 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("User"); }); - modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => - { - b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") - .WithMany("Backups") - .HasForeignKey("KeycloakRealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") - .WithMany() - .HasForeignKey("StorageLinkId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Realm"); - - b.Navigation("StorageLink"); - }); - - modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => { b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") .WithMany() @@ -1769,6 +3391,11 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasForeignKey("CnpgDatabaseId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") .WithMany() .HasForeignKey("TenantId") @@ -1779,6 +3406,95 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("CnpgDatabase"); + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("Tenant"); }); @@ -1790,6 +3506,11 @@ namespace EntKube.Web.Data.Migrations.Postgres .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.App", "LinkedApp") .WithMany() .HasForeignKey("LinkedAppId") @@ -1798,6 +3519,19 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("ComponentConfig"); b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); }); modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => @@ -1819,6 +3553,43 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") @@ -1867,6 +3638,36 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("MongoCluster"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1878,6 +3679,118 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1889,6 +3802,31 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") @@ -1990,6 +3928,11 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasForeignKey("AppId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") .WithMany() .HasForeignKey("CnpgDatabaseId") @@ -2000,6 +3943,16 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasForeignKey("ComponentId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") .WithMany() .HasForeignKey("MongoClusterId") @@ -2010,6 +3963,21 @@ namespace EntKube.Web.Data.Migrations.Postgres .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") .WithMany() .HasForeignKey("StorageLinkId"); @@ -2020,19 +3988,86 @@ namespace EntKube.Web.Data.Migrations.Postgres .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); + b.Navigation("CnpgCluster"); + b.Navigation("CnpgDatabase"); b.Navigation("Component"); + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + b.Navigation("MongoCluster"); b.Navigation("MongoDatabase"); + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("StorageLink"); b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -2086,17 +4121,34 @@ namespace EntKube.Web.Data.Migrations.Postgres .IsRequired(); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Navigation("AppEnvironments"); b.Navigation("Deployments"); + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + b.Navigation("Secrets"); }); modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + b.Navigation("DatabaseBindings"); b.Navigation("Manifests"); @@ -2106,6 +4158,11 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("StorageBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Navigation("ExternalRoutes"); @@ -2144,6 +4201,13 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Navigation("Memberships"); @@ -2159,6 +4223,11 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Backups"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Navigation("Components"); @@ -2176,11 +4245,31 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Navigation("StorageLinks"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Navigation("Secrets"); @@ -2197,6 +4286,10 @@ namespace EntKube.Web.Data.Migrations.Postgres b.Navigation("Environments"); + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + b.Navigation("Groups"); b.Navigation("KubernetesClusters"); @@ -2212,6 +4305,18 @@ namespace EntKube.Web.Data.Migrations.Postgres { b.Navigation("Memberships"); }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); #pragma warning restore 612, 618 } } diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.Designer.cs new file mode 100644 index 0000000..18061e8 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.Designer.cs @@ -0,0 +1,2226 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260521201903_AddExternalKeycloak")] + partial class AddExternalKeycloak + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.cs new file mode 100644 index 0000000..7360d3f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260521201903_AddExternalKeycloak.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddExternalKeycloak : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "uniqueidentifier", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uniqueidentifier"); + + migrationBuilder.AddColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs", + type: "nvarchar(200)", + maxLength: 200, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs"); + + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "uniqueidentifier", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uniqueidentifier", + oldNullable: true); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.Designer.cs new file mode 100644 index 0000000..e59a579 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.Designer.cs @@ -0,0 +1,2290 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260522065956_AddKeycloakTheme")] + partial class AddKeycloakTheme + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.cs new file mode 100644 index 0000000..e422137 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522065956_AddKeycloakTheme.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddKeycloakTheme : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "KeycloakThemes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + KeycloakComponentConfigId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + LoginTheme = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + AccountTheme = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KeycloakThemes", x => x.Id); + table.ForeignKey( + name: "FK_KeycloakThemes_KeycloakComponentConfigs_KeycloakComponentConfigId", + column: x => x.KeycloakComponentConfigId, + principalTable: "KeycloakComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakThemes_KeycloakComponentConfigId_Name", + table: "KeycloakThemes", + columns: new[] { "KeycloakComponentConfigId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId", + principalTable: "KeycloakThemes", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropTable( + name: "KeycloakThemes"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.Designer.cs new file mode 100644 index 0000000..0a23f2f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.Designer.cs @@ -0,0 +1,2456 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260522091254_AddRegisteredPostgres")] + partial class AddRegisteredPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.cs new file mode 100644 index 0000000..10cbbfa --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522091254_AddRegisteredPostgres.cs @@ -0,0 +1,190 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddRegisteredPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresInstances", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + KubernetesClusterId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Namespace = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + ServiceName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: false), + Port = table.Column(type: "int", nullable: false), + AdminPodName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: false), + AdminUsername = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresInstances", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresDatabases", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RegisteredPostgresInstanceId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + Owner = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + Status = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDatabases", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDatabases_RegisteredPostgresInstances_RegisteredPostgresInstanceId", + column: x => x.RegisteredPostgresInstanceId, + principalTable: "RegisteredPostgresInstances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDatabases_RegisteredPostgresInstanceId_Name", + table: "RegisteredPostgresDatabases", + columns: new[] { "RegisteredPostgresInstanceId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_KubernetesClusterId", + table: "RegisteredPostgresInstances", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_TenantId_Name", + table: "RegisteredPostgresInstances", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + + migrationBuilder.DropForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresDatabases"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresInstances"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.Designer.cs new file mode 100644 index 0000000..dbe5c1f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.Designer.cs @@ -0,0 +1,2507 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260522110456_AddRegisteredPostgresDump")] + partial class AddRegisteredPostgresDump + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.cs new file mode 100644 index 0000000..6d606d1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260522110456_AddRegisteredPostgresDump.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddRegisteredPostgresDump : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RegisteredPostgresDumps", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RegisteredPostgresDatabaseId = table.Column(type: "uniqueidentifier", nullable: false), + StorageLinkId = table.Column(type: "uniqueidentifier", nullable: false), + S3Key = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false), + SizeBytes = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDumps", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + column: x => x.RegisteredPostgresDatabaseId, + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_RegisteredPostgresDatabaseId", + table: "RegisteredPostgresDumps", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_StorageLinkId", + table: "RegisteredPostgresDumps", + column: "StorageLinkId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RegisteredPostgresDumps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.Designer.cs new file mode 100644 index 0000000..1bc1b60 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.Designer.cs @@ -0,0 +1,2519 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260524194112_AddCnpgClusterVaultSecret")] + partial class AddCnpgClusterVaultSecret + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.cs new file mode 100644 index 0000000..c004a5a --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260524194112_AddCnpgClusterVaultSecret.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddCnpgClusterVaultSecret : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CnpgClusterId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId", + principalTable: "CnpgClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "CnpgClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.Designer.cs new file mode 100644 index 0000000..f22ee87 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.Designer.cs @@ -0,0 +1,2518 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260525203017_KeycloakBackupStorageLinkNullable")] + partial class KeycloakBackupStorageLinkNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.cs new file mode 100644 index 0000000..be66e3b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260525203017_KeycloakBackupStorageLinkNullable.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class KeycloakBackupStorageLinkNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "uniqueidentifier", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uniqueidentifier"); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "uniqueidentifier", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uniqueidentifier", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.Designer.cs new file mode 100644 index 0000000..d9a9e34 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.Designer.cs @@ -0,0 +1,2594 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260526050105_AddHarborComponentConfig")] + partial class AddHarborComponentConfig + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.cs new file mode 100644 index 0000000..53d9117 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526050105_AddHarborComponentConfig.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddHarborComponentConfig : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborComponentConfigs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + ClusterComponentId = table.Column(type: "uniqueidentifier", nullable: false), + CnpgDatabaseId = table.Column(type: "uniqueidentifier", nullable: true), + StorageLinkId = table.Column(type: "uniqueidentifier", nullable: true), + AdminUsername = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + RegistryUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborComponentConfigs", x => x.Id); + table.ForeignKey( + name: "FK_HarborComponentConfigs_ClusterComponents_ClusterComponentId", + column: x => x.ClusterComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborComponentConfigs_CnpgDatabases_CnpgDatabaseId", + column: x => x.CnpgDatabaseId, + principalTable: "CnpgDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_ClusterComponentId", + table: "HarborComponentConfigs", + column: "ClusterComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_CnpgDatabaseId", + table: "HarborComponentConfigs", + column: "CnpgDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_StorageLinkId", + table: "HarborComponentConfigs", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_TenantId", + table: "HarborComponentConfigs", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborComponentConfigs"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.Designer.cs new file mode 100644 index 0000000..59193dd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.Designer.cs @@ -0,0 +1,2598 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260526113541_AddCnpgMaxBackups")] + partial class AddCnpgMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.cs new file mode 100644 index 0000000..e84a3de --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526113541_AddCnpgMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddCnpgMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "CnpgClusters", + type: "int", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..11eb969 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.Designer.cs @@ -0,0 +1,2597 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260526114844_FixCnpgMaxBackupsDefault")] + partial class FixCnpgMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.cs new file mode 100644 index 0000000..73756a5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526114844_FixCnpgMaxBackupsDefault.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class FixCnpgMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + "UPDATE [CnpgClusters] SET [MaxBackups] = 20 WHERE [MaxBackups] = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.Designer.cs new file mode 100644 index 0000000..74093d1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.Designer.cs @@ -0,0 +1,2601 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260526121554_AddMongoMaxBackups")] + partial class AddMongoMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("int"); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.cs new file mode 100644 index 0000000..4b675a1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121554_AddMongoMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMongoMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "MongoClusters", + type: "int", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..99a3f1d --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.Designer.cs @@ -0,0 +1,2604 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260526121840_FixMongoMaxBackupsDefault")] + partial class FixMongoMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.cs new file mode 100644 index 0000000..6f80857 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260526121840_FixMongoMaxBackupsDefault.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class FixMongoMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + "UPDATE [MongoClusters] SET [MaxBackups] = 20 WHERE [MaxBackups] = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.Designer.cs new file mode 100644 index 0000000..37cf8d0 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.Designer.cs @@ -0,0 +1,2699 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260527143418_AddDockerRegistryCredential")] + partial class AddDockerRegistryCredential + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.cs new file mode 100644 index 0000000..828c376 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260527143418_AddDockerRegistryCredential.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddDockerRegistryCredential : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DockerRegistryCredentials", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + VaultId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + RegistryType = table.Column(type: "int", nullable: false), + Server = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Username = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: false), + EncryptedPassword = table.Column(type: "varbinary(max)", nullable: false), + PasswordNonce = table.Column(type: "varbinary(max)", nullable: false), + Email = table.Column(type: "nvarchar(254)", maxLength: 254, nullable: true), + AppId = table.Column(type: "uniqueidentifier", nullable: true), + KubernetesClusterId = table.Column(type: "uniqueidentifier", nullable: true), + KubernetesSecretName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: true), + KubernetesNamespace = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DockerRegistryCredentials", x => x.Id); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_SecretVaults_VaultId", + column: x => x.VaultId, + principalTable: "SecretVaults", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_AppId", + table: "DockerRegistryCredentials", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_KubernetesClusterId", + table: "DockerRegistryCredentials", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_VaultId", + table: "DockerRegistryCredentials", + column: "VaultId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DockerRegistryCredentials"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.Designer.cs new file mode 100644 index 0000000..e50ce74 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.Designer.cs @@ -0,0 +1,2711 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260527151555_AddAppSecretClusterId")] + partial class AddAppSecretClusterId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.cs new file mode 100644 index 0000000..98cc884 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260527151555_AddAppSecretClusterId.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddAppSecretClusterId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KubernetesClusterId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId", + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "KubernetesClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.Designer.cs new file mode 100644 index 0000000..214bdea --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.Designer.cs @@ -0,0 +1,2773 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260528095229_AddAuditEventsAndRouteHealth")] + partial class AddAuditEventsAndRouteHealth + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.cs new file mode 100644 index 0000000..b0601bc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260528095229_AddAuditEventsAndRouteHealth.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddAuditEventsAndRouteHealth : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsReachable", + table: "ExternalRoutes", + type: "bit", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastStatusCode", + table: "ExternalRoutes", + type: "int", + nullable: true); + + migrationBuilder.CreateTable( + name: "AuditEvents", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + DeploymentId = table.Column(type: "uniqueidentifier", nullable: true), + PerformedBy = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Action = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + ResourceKind = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + ResourceName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: true), + Details = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + OccurredAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuditEvents", x => x.Id); + table.ForeignKey( + name: "FK_AuditEvents_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_DeploymentId", + table: "AuditEvents", + column: "DeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_OccurredAt", + table: "AuditEvents", + column: "OccurredAt"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuditEvents"); + + migrationBuilder.DropColumn( + name: "IsReachable", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastStatusCode", + table: "ExternalRoutes"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.Designer.cs new file mode 100644 index 0000000..4829d6e --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.Designer.cs @@ -0,0 +1,2834 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601071043_AddHarborProject")] + partial class AddHarborProject + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.cs new file mode 100644 index 0000000..83b4a27 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601071043_AddHarborProject.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddHarborProject : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborProjects", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + HarborComponentConfigId = table.Column(type: "uniqueidentifier", nullable: false), + ProjectName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), + LinkedAppId = table.Column(type: "uniqueidentifier", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborProjects", x => x.Id); + table.ForeignKey( + name: "FK_HarborProjects_Apps_LinkedAppId", + column: x => x.LinkedAppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborProjects_HarborComponentConfigs_HarborComponentConfigId", + column: x => x.HarborComponentConfigId, + principalTable: "HarborComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborProjects_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_HarborComponentConfigId_ProjectName", + table: "HarborProjects", + columns: new[] { "HarborComponentConfigId", "ProjectName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_LinkedAppId", + table: "HarborProjects", + column: "LinkedAppId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_TenantId", + table: "HarborProjects", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborProjects"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.Designer.cs new file mode 100644 index 0000000..8721c2c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.Designer.cs @@ -0,0 +1,3004 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601124725_AddRabbitMQ")] + partial class AddRabbitMQ + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.cs new file mode 100644 index 0000000..38994fa --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601124725_AddRabbitMQ.cs @@ -0,0 +1,127 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddRabbitMQ : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RabbitMQClusters", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + KubernetesClusterId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + Namespace = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + RabbitMQVersion = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Replicas = table.Column(type: "int", nullable: false), + StorageSize = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: true), + StorageLinkId = table.Column(type: "uniqueidentifier", nullable: true), + BackupSchedule = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + MaxBackups = table.Column(type: "int", nullable: false, defaultValue: 10), + Status = table.Column(type: "int", nullable: false), + LastError = table.Column(type: "nvarchar(max)", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQClusters", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQClusters_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_RabbitMQClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RabbitMQBackups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RabbitMQClusterId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + StorageLinkId = table.Column(type: "uniqueidentifier", nullable: true), + ObjectKey = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: false), + ClusterName = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + SizeBytes = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "int", nullable: false), + LastError = table.Column(type: "nvarchar(max)", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + CompletedAt = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQBackups", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQBackups_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQBackups_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_RabbitMQClusterId", + table: "RabbitMQBackups", + column: "RabbitMQClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_StorageLinkId", + table: "RabbitMQBackups", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_KubernetesClusterId_Name_Namespace", + table: "RabbitMQClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_StorageLinkId", + table: "RabbitMQClusters", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_TenantId", + table: "RabbitMQClusters", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RabbitMQBackups"); + + migrationBuilder.DropTable( + name: "RabbitMQClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.Designer.cs new file mode 100644 index 0000000..0e83411 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.Designer.cs @@ -0,0 +1,3016 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601132344_AddRabbitMQCredentialVaultSync")] + partial class AddRabbitMQCredentialVaultSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.cs new file mode 100644 index 0000000..660d629 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601132344_AddRabbitMQCredentialVaultSync.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddRabbitMQCredentialVaultSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId", + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.Designer.cs new file mode 100644 index 0000000..a2a6772 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.Designer.cs @@ -0,0 +1,3019 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601160424_AddMongoClusterIsExternal")] + partial class AddMongoClusterIsExternal + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.cs new file mode 100644 index 0000000..b27fcb2 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601160424_AddMongoClusterIsExternal.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMongoClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "MongoClusters", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.Designer.cs new file mode 100644 index 0000000..6e3ab6f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.Designer.cs @@ -0,0 +1,3311 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601163401_AddMonitoringAndAlerting")] + partial class AddMonitoringAndAlerting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.cs new file mode 100644 index 0000000..8a19b0f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601163401_AddMonitoringAndAlerting.cs @@ -0,0 +1,205 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMonitoringAndAlerting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AlertIncidents", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ClusterId = table.Column(type: "uniqueidentifier", nullable: false), + Fingerprint = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + AlertName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Severity = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Summary = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Description = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), + LabelsJson = table.Column(type: "nvarchar(max)", nullable: false), + StartsAt = table.Column(type: "datetime2", nullable: false), + EndsAt = table.Column(type: "datetime2", nullable: true), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + AcknowledgedBy = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + AcknowledgedAt = table.Column(type: "datetime2", nullable: true), + ResolvedAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AlertIncidents", x => x.Id); + table.ForeignKey( + name: "FK_AlertIncidents_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DeploymentHealthSnapshots", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + DeploymentId = table.Column(type: "uniqueidentifier", nullable: false), + HealthStatus = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + SyncStatus = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + ReadyReplicas = table.Column(type: "int", nullable: true), + TotalReplicas = table.Column(type: "int", nullable: true), + SnapshotAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeploymentHealthSnapshots", x => x.Id); + table.ForeignKey( + name: "FK_DeploymentHealthSnapshots_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationChannels", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Type = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + ConfigurationJson = table.Column(type: "nvarchar(max)", nullable: false), + IsEnabled = table.Column(type: "bit", nullable: false), + SeverityFilter = table.Column(type: "nvarchar(30)", maxLength: 30, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationChannels", x => x.Id); + table.ForeignKey( + name: "FK_NotificationChannels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IncidentNotes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + IncidentId = table.Column(type: "uniqueidentifier", nullable: false), + Author = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Content = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IncidentNotes", x => x.Id); + table.ForeignKey( + name: "FK_IncidentNotes_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationDeliveries", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + IncidentId = table.Column(type: "uniqueidentifier", nullable: false), + ChannelId = table.Column(type: "uniqueidentifier", nullable: false), + IsFiring = table.Column(type: "bit", nullable: false), + SentAt = table.Column(type: "datetime2", nullable: false), + Success = table.Column(type: "bit", nullable: false), + Error = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationDeliveries", x => x.Id); + table.ForeignKey( + name: "FK_NotificationDeliveries_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_NotificationDeliveries_NotificationChannels_ChannelId", + column: x => x.ChannelId, + principalTable: "NotificationChannels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_ClusterId_Fingerprint", + table: "AlertIncidents", + columns: new[] { "ClusterId", "Fingerprint" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_StartsAt", + table: "AlertIncidents", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_Status", + table: "AlertIncidents", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_DeploymentHealthSnapshots_DeploymentId_SnapshotAt", + table: "DeploymentHealthSnapshots", + columns: new[] { "DeploymentId", "SnapshotAt" }); + + migrationBuilder.CreateIndex( + name: "IX_IncidentNotes_IncidentId", + table: "IncidentNotes", + column: "IncidentId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationChannels_TenantId_Name", + table: "NotificationChannels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_ChannelId", + table: "NotificationDeliveries", + column: "ChannelId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_IncidentId_ChannelId_IsFiring", + table: "NotificationDeliveries", + columns: new[] { "IncidentId", "ChannelId", "IsFiring" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeploymentHealthSnapshots"); + + migrationBuilder.DropTable( + name: "IncidentNotes"); + + migrationBuilder.DropTable( + name: "NotificationDeliveries"); + + migrationBuilder.DropTable( + name: "AlertIncidents"); + + migrationBuilder.DropTable( + name: "NotificationChannels"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs new file mode 100644 index 0000000..86d2888 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs @@ -0,0 +1,3475 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory")] + partial class AddMaintenanceWindowsSlaTargetsRouteHistory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.cs new file mode 100644 index 0000000..357ede0 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601181722_AddMaintenanceWindowsSlaTargetsRouteHistory.cs @@ -0,0 +1,152 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMaintenanceWindowsSlaTargetsRouteHistory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ExternalRouteHealthHistories", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RouteId = table.Column(type: "uniqueidentifier", nullable: false), + IsReachable = table.Column(type: "bit", nullable: false), + StatusCode = table.Column(type: "int", nullable: true), + ResponseMs = table.Column(type: "int", nullable: true), + CheckedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExternalRouteHealthHistories", x => x.Id); + table.ForeignKey( + name: "FK_ExternalRouteHealthHistories_ExternalRoutes_RouteId", + column: x => x.RouteId, + principalTable: "ExternalRoutes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MaintenanceWindows", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + ClusterId = table.Column(type: "uniqueidentifier", nullable: true), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + StartsAt = table.Column(type: "datetime2", nullable: false), + EndsAt = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceWindows", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceWindows_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_MaintenanceWindows_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SlaTargets", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + CustomerId = table.Column(type: "uniqueidentifier", nullable: true), + AppId = table.Column(type: "uniqueidentifier", nullable: true), + TargetPercent = table.Column(type: "float", nullable: false), + MeasurementWindowDays = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SlaTargets", x => x.Id); + table.ForeignKey( + name: "FK_SlaTargets_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExternalRouteHealthHistories_RouteId_CheckedAt", + table: "ExternalRouteHealthHistories", + columns: new[] { "RouteId", "CheckedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_ClusterId", + table: "MaintenanceWindows", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_StartsAt", + table: "MaintenanceWindows", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_TenantId_StartsAt", + table: "MaintenanceWindows", + columns: new[] { "TenantId", "StartsAt" }); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_AppId", + table: "SlaTargets", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_CustomerId", + table: "SlaTargets", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_TenantId_CustomerId_AppId", + table: "SlaTargets", + columns: new[] { "TenantId", "CustomerId", "AppId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ExternalRouteHealthHistories"); + + migrationBuilder.DropTable( + name: "MaintenanceWindows"); + + migrationBuilder.DropTable( + name: "SlaTargets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.Designer.cs new file mode 100644 index 0000000..471c403 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.Designer.cs @@ -0,0 +1,3545 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601190013_AddMessagingBinding")] + partial class AddMessagingBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.cs new file mode 100644 index 0000000..5b09f01 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601190013_AddMessagingBinding.cs @@ -0,0 +1,65 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMessagingBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MessagingBindings", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RabbitMQClusterId = table.Column(type: "uniqueidentifier", nullable: false), + AppDeploymentId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Vhost = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + QueueName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true), + ExchangeName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true), + KubernetesSecretName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "bit", nullable: false), + LastSyncedAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MessagingBindings", x => x.Id); + table.ForeignKey( + name: "FK_MessagingBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MessagingBindings_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_AppDeploymentId", + table: "MessagingBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_RabbitMQClusterId", + table: "MessagingBindings", + column: "RabbitMQClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MessagingBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.Designer.cs new file mode 100644 index 0000000..a8c532f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.Designer.cs @@ -0,0 +1,3548 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260601200000_AddMongoClusterExternalUri")] + partial class AddMongoClusterExternalUri + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.cs new file mode 100644 index 0000000..f73840a --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260601200000_AddMongoClusterExternalUri.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddMongoClusterExternalUri : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExternalUri", + table: "MongoClusters", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExternalUri", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.Designer.cs new file mode 100644 index 0000000..47bb02c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.Designer.cs @@ -0,0 +1,3775 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260602091242_AddVpnTunnels")] + partial class AddVpnTunnels + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.cs new file mode 100644 index 0000000..f5da1dd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602091242_AddVpnTunnels.cs @@ -0,0 +1,176 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddVpnTunnels : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "VpnTunnels", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + TunnelType = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + IkeVersion = table.Column(type: "int", nullable: false), + IkeProposal = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + EspProposal = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + DpdDelay = table.Column(type: "int", nullable: false), + DpdTimeout = table.Column(type: "int", nullable: false), + LastStatusCheckAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnTunnels", x => x.Id); + table.ForeignKey( + name: "FK_VpnTunnels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnLocalEndpoints", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + VpnTunnelId = table.Column(type: "uniqueidentifier", nullable: false), + ClusterId = table.Column(type: "uniqueidentifier", nullable: false), + ComponentId = table.Column(type: "uniqueidentifier", nullable: true), + Subnets = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), + PublicIp = table.Column(type: "nvarchar(45)", maxLength: 45, nullable: true), + Role = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnLocalEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_ClusterComponents_ComponentId", + column: x => x.ComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnRemoteEndpoints", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + VpnTunnelId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + PublicIp = table.Column(type: "nvarchar(45)", maxLength: 45, nullable: false), + Subnets = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), + AuthMode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + LastSeenAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnRemoteEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnRemoteEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ClusterId", + table: "VpnLocalEndpoints", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ComponentId", + table: "VpnLocalEndpoints", + column: "ComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_VpnTunnelId", + table: "VpnLocalEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnRemoteEndpoints_VpnTunnelId", + table: "VpnRemoteEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnTunnels_TenantId_Name", + table: "VpnTunnels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId", + principalTable: "VpnRemoteEndpoints", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "VpnLocalEndpoints"); + + migrationBuilder.DropTable( + name: "VpnRemoteEndpoints"); + + migrationBuilder.DropTable( + name: "VpnTunnels"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.Designer.cs new file mode 100644 index 0000000..ee70b60 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.Designer.cs @@ -0,0 +1,3789 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260602103459_AddVpnLifetimeAndIdentity")] + partial class AddVpnLifetimeAndIdentity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.cs new file mode 100644 index 0000000..96ae2fc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602103459_AddVpnLifetimeAndIdentity.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddVpnLifetimeAndIdentity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ChildLifetime", + table: "VpnTunnels", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "IkeLifetime", + table: "VpnTunnels", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "LocalId", + table: "VpnRemoteEndpoints", + type: "nvarchar(500)", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints", + type: "nvarchar(500)", + maxLength: 500, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ChildLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "IkeLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "LocalId", + table: "VpnRemoteEndpoints"); + + migrationBuilder.DropColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.Designer.cs new file mode 100644 index 0000000..a43e2bc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.Designer.cs @@ -0,0 +1,3881 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260602124617_AddRedisCluster")] + partial class AddRedisCluster + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterSize") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("bit"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.cs new file mode 100644 index 0000000..64db538 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602124617_AddRedisCluster.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddRedisCluster : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RedisClusterId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "RedisClusters", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + KubernetesClusterId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + Namespace = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + ClusterSize = table.Column(type: "int", nullable: false), + RedisVersion = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + StorageSize = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: true), + PersistenceEnabled = table.Column(type: "bit", nullable: false), + Status = table.Column(type: "int", nullable: false), + LastError = table.Column(type: "nvarchar(max)", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RedisClusters", x => x.Id); + table.ForeignKey( + name: "FK_RedisClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RedisClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_KubernetesClusterId_Name_Namespace", + table: "RedisClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_TenantId", + table: "RedisClusters", + column: "TenantId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId", + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RedisClusters"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RedisClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.Designer.cs new file mode 100644 index 0000000..41c60af --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.Designer.cs @@ -0,0 +1,3940 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260602185552_AddCacheBinding")] + partial class AddCacheBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterSize") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("bit"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.cs new file mode 100644 index 0000000..228c925 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602185552_AddCacheBinding.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddCacheBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CacheBindings", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RedisClusterId = table.Column(type: "uniqueidentifier", nullable: false), + AppDeploymentId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + KubernetesSecretName = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "bit", nullable: false), + LastSyncedAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CacheBindings", x => x.Id); + table.ForeignKey( + name: "FK_CacheBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CacheBindings_RedisClusters_RedisClusterId", + column: x => x.RedisClusterId, + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_AppDeploymentId", + table: "CacheBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_RedisClusterId", + table: "CacheBindings", + column: "RedisClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CacheBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260602200000_AddMongoClusterResources.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260602200000_AddMongoClusterResources.cs new file mode 100644 index 0000000..8c4cb96 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260602200000_AddMongoClusterResources.cs @@ -0,0 +1,51 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260602200000_AddMongoClusterResources")] + public partial class AddMongoClusterResources : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CpuRequest", + table: "MongoClusters", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "CpuLimit", + table: "MongoClusters", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryRequest", + table: "MongoClusters", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryLimit", + table: "MongoClusters", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "CpuRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "CpuLimit", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryLimit", table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260603080000_AddCnpgClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260603080000_AddCnpgClusterIsExternal.cs new file mode 100644 index 0000000..a236b0c --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260603080000_AddCnpgClusterIsExternal.cs @@ -0,0 +1,33 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260603080000_AddCnpgClusterIsExternal")] + public partial class AddCnpgClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "CnpgClusters", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.Designer.cs new file mode 100644 index 0000000..96a2de1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.Designer.cs @@ -0,0 +1,4137 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260603215335_AddGitSync")] + partial class AddGitSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("GitAutoSync") + .HasColumnType("bit"); + + b.Property("GitLastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("SourceFile") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TrustedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CpuLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("CpuRequest") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MemoryLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("MemoryRequest") + .HasColumnType("nvarchar(max)"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterSize") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("bit"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.cs new file mode 100644 index 0000000..aa32cf7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260603215335_AddGitSync.cs @@ -0,0 +1,251 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddGitSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "VaultSecrets", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.AddColumn( + name: "SourceFile", + table: "DeploymentManifests", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitAutoSync", + table: "AppDeployments", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "GitLastSyncedAt", + table: "AppDeployments", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments", + type: "nvarchar(40)", + maxLength: 40, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitPath", + table: "AppDeployments", + type: "nvarchar(500)", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "AppDeployments", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRevision", + table: "AppDeployments", + type: "nvarchar(200)", + maxLength: 200, + nullable: true); + + migrationBuilder.AddColumn( + name: "ParentDeploymentId", + table: "AppDeployments", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "GitRepositories", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Url = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), + AuthType = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Username = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + DefaultBranch = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false, defaultValue: "main"), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GitRepositories", x => x.Id); + table.ForeignKey( + name: "FK_GitRepositories_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GitKnownHosts", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false), + Hostname = table.Column(type: "nvarchar(253)", maxLength: 253, nullable: false), + Fingerprint = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + KeyType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + TrustedAt = table.Column(type: "datetime2", nullable: false), + GitRepositoryId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GitKnownHosts", x => x.Id); + table.ForeignKey( + name: "FK_GitKnownHosts_GitRepositories_GitRepositoryId", + column: x => x.GitRepositoryId, + principalTable: "GitRepositories", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_GitKnownHosts_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_GitRepositoryId", + table: "GitKnownHosts", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_TenantId_Hostname", + table: "GitKnownHosts", + columns: new[] { "TenantId", "Hostname" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GitRepositories_TenantId_Name", + table: "GitRepositories", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId", + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "GitKnownHosts"); + + migrationBuilder.DropTable( + name: "GitRepositories"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "SourceFile", + table: "DeploymentManifests"); + + migrationBuilder.DropColumn( + name: "GitAutoSync", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedAt", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitPath", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRevision", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "ParentDeploymentId", + table: "AppDeployments"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.Designer.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.Designer.cs new file mode 100644 index 0000000..225aadc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.Designer.cs @@ -0,0 +1,4328 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerApplicationDbContext))] + [Migration("20260604060626_AddAppGovernance")] + partial class AddAppGovernance + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("GitAutoSync") + .HasColumnType("bit"); + + b.Property("GitLastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAt") + .HasColumnType("datetime2"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CustomYaml") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("MaxPods") + .HasColumnType("int"); + + b.Property("MaxPvcs") + .HasColumnType("int"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("AutoMountToken") + .HasColumnType("bit"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AppRbacPolicyId") + .HasColumnType("uniqueidentifier"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Configuration") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HelmChartName") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmChartVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmRepoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HelmValues") + .HasColumnType("nvarchar(max)"); + + b.Property("InstalledAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Instances") + .HasColumnType("int"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("SourceFile") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("ParentResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ServicePort") + .HasColumnType("int"); + + b.Property("TlsCertificate") + .HasColumnType("nvarchar(max)"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TlsPrivateKey") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TrustedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("GroupId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakRealmId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasColumnType("nvarchar(max)"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ContextName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Kubeconfig") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CpuLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("CpuRequest") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("int"); + + b.Property("MemoryLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("MemoryRequest") + .HasColumnType("nvarchar(max)"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserDomainName") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterSize") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("bit"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BucketName") + .HasColumnType("nvarchar(max)"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + + b.Property("EnvironmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("JoinedAt") + .HasColumnType("datetime2"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("MongoClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("MongoDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OpenStackConnectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncToKubernetes") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.cs b/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.cs new file mode 100644 index 0000000..d94dccb --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/SqlServer/20260604060626_AddAppGovernance.cs @@ -0,0 +1,153 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.SqlServer +{ + /// + public partial class AddAppGovernance : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Namespace", + table: "Apps", + type: "nvarchar(63)", + maxLength: 63, + nullable: true); + + migrationBuilder.CreateTable( + name: "AppNetworkPolicies", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AppId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + PolicyType = table.Column(type: "nvarchar(30)", maxLength: 30, nullable: false), + AllowFromNamespace = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: true), + CustomYaml = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AppNetworkPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppNetworkPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppQuotas", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AppId = table.Column(type: "uniqueidentifier", nullable: false), + CpuRequest = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + CpuLimit = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + MemoryRequest = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + MemoryLimit = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + MaxPods = table.Column(type: "int", nullable: true), + MaxPvcs = table.Column(type: "int", nullable: true), + UpdatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppQuotas", x => x.Id); + table.ForeignKey( + name: "FK_AppQuotas_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacPolicies", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AppId = table.Column(type: "uniqueidentifier", nullable: false), + ServiceAccountName = table.Column(type: "nvarchar(63)", maxLength: 63, nullable: false), + AutoMountToken = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacRules", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AppRbacPolicyId = table.Column(type: "uniqueidentifier", nullable: false), + ApiGroups = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Resources = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Verbs = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacRules", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacRules_AppRbacPolicies_AppRbacPolicyId", + column: x => x.AppRbacPolicyId, + principalTable: "AppRbacPolicies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AppNetworkPolicies_AppId_Name", + table: "AppNetworkPolicies", + columns: new[] { "AppId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppQuotas_AppId", + table: "AppQuotas", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacPolicies_AppId", + table: "AppRbacPolicies", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacRules_AppRbacPolicyId", + table: "AppRbacRules", + column: "AppRbacPolicyId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AppNetworkPolicies"); + + migrationBuilder.DropTable( + name: "AppQuotas"); + + migrationBuilder.DropTable( + name: "AppRbacRules"); + + migrationBuilder.DropTable( + name: "AppRbacPolicies"); + + migrationBuilder.DropColumn( + name: "Namespace", + table: "Apps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/SqlServer/SqlServerApplicationDbContextModelSnapshot.cs b/src/EntKube.Web/Data/Migrations/SqlServer/SqlServerApplicationDbContextModelSnapshot.cs index 47f65a4..d7de04b 100644 --- a/src/EntKube.Web/Data/Migrations/SqlServer/SqlServerApplicationDbContextModelSnapshot.cs +++ b/src/EntKube.Web/Data/Migrations/SqlServer/SqlServerApplicationDbContextModelSnapshot.cs @@ -22,6 +22,83 @@ namespace EntKube.Web.Data.Migrations.SqlServer SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime2"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Property("Id") @@ -39,6 +116,10 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasMaxLength(200) .HasColumnType("nvarchar(200)"); + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + b.HasKey("Id"); b.HasIndex("CustomerId", "Name") @@ -65,6 +146,27 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("EnvironmentId") .HasColumnType("uniqueidentifier"); + b.Property("GitAutoSync") + .HasColumnType("bit"); + + b.Property("GitLastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("HealthStatus") .IsRequired() .HasMaxLength(20) @@ -98,6 +200,9 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasMaxLength(63) .HasColumnType("nvarchar(63)"); + b.Property("ParentDeploymentId") + .HasColumnType("uniqueidentifier"); + b.Property("StatusMessage") .HasColumnType("nvarchar(max)"); @@ -117,6 +222,10 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.HasIndex("EnvironmentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + b.HasIndex("AppId", "Name") .IsUnique(); @@ -141,6 +250,138 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("AppEnvironments"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CustomYaml") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("MaxPods") + .HasColumnType("int"); + + b.Property("MaxPvcs") + .HasColumnType("int"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("AutoMountToken") + .HasColumnType("bit"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AppRbacPolicyId") + .HasColumnType("uniqueidentifier"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => { b.Property("Id") @@ -206,6 +447,87 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("AspNetUsers", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OccurredAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Property("Id") @@ -322,12 +644,20 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("Instances") .HasColumnType("int"); + b.Property("IsExternal") + .HasColumnType("bit"); + b.Property("KubernetesClusterId") .HasColumnType("uniqueidentifier"); b.Property("LastError") .HasColumnType("nvarchar(max)"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + b.Property("Name") .IsRequired() .HasMaxLength(63) @@ -479,6 +809,9 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("MongoDatabaseId") .HasColumnType("uniqueidentifier"); + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + b.Property("SyncEnabled") .HasColumnType("bit"); @@ -490,9 +823,46 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.HasIndex("MongoDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.ToTable("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ReadyReplicas") + .HasColumnType("int"); + + b.Property("SnapshotAt") + .HasColumnType("datetime2"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TotalReplicas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => { b.Property("Id") @@ -518,6 +888,9 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("SortOrder") .HasColumnType("int"); + b.Property("SourceFile") + .HasColumnType("nvarchar(max)"); + b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -596,6 +969,76 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("DeploymentResources"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("nvarchar(254)"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("VaultId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.Property("Id") @@ -650,6 +1093,15 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasMaxLength(253) .HasColumnType("nvarchar(253)"); + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("LastHealthCheckAt") + .HasColumnType("datetime2"); + + b.Property("LastStatusCode") + .HasColumnType("int"); + b.Property("PathPrefix") .IsRequired() .HasMaxLength(200) @@ -680,6 +1132,120 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("ExternalRoutes"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CheckedAt") + .HasColumnType("datetime2"); + + b.Property("IsReachable") + .HasColumnType("bit"); + + b.Property("ResponseMs") + .HasColumnType("int"); + + b.Property("RouteId") + .HasColumnType("uniqueidentifier"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TrustedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Property("Id") @@ -723,6 +1289,113 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("GroupMemberships"); }); + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClusterComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CnpgDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("HarborComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkedAppId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => { b.Property("Id") @@ -757,7 +1430,7 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("Status") .HasColumnType("int"); - b.Property("StorageLinkId") + b.Property("StorageLinkId") .HasColumnType("uniqueidentifier"); b.Property("TenantId") @@ -787,7 +1460,7 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("ClusterComponentId") + b.Property("ClusterComponentId") .HasColumnType("uniqueidentifier"); b.Property("CnpgDatabaseId") @@ -796,6 +1469,13 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("CreatedAt") .HasColumnType("datetime2"); + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + b.Property("TenantId") .HasColumnType("uniqueidentifier"); @@ -805,6 +1485,8 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.HasIndex("CnpgDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("TenantId"); b.ToTable("KeycloakComponentConfigs"); @@ -833,6 +1515,9 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("KeycloakComponentConfigId") .HasColumnType("uniqueidentifier"); + b.Property("KeycloakThemeId") + .HasColumnType("uniqueidentifier"); + b.Property("LinkedAppId") .HasColumnType("uniqueidentifier"); @@ -849,6 +1534,8 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.HasKey("Id"); + b.HasIndex("KeycloakThemeId"); + b.HasIndex("LinkedAppId"); b.HasIndex("KeycloakComponentConfigId", "RealmName") @@ -857,6 +1544,42 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("KeycloakRealms"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Property("Id") @@ -898,6 +1621,103 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndsAt") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppDeploymentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SyncEnabled") + .HasColumnType("bit"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.Property("Id") @@ -945,18 +1765,41 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("CpuLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("CpuRequest") + .HasColumnType("nvarchar(max)"); + b.Property("CreatedAt") .HasColumnType("datetime2"); + b.Property("ExternalUri") + .HasColumnType("nvarchar(max)"); + + b.Property("IsExternal") + .HasColumnType("bit"); + b.Property("KubernetesClusterId") .HasColumnType("uniqueidentifier"); b.Property("LastError") .HasColumnType("nvarchar(max)"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + b.Property("Members") .HasColumnType("int"); + b.Property("MemoryLimit") + .HasColumnType("nvarchar(max)"); + + b.Property("MemoryRequest") + .HasColumnType("nvarchar(max)"); + b.Property("MongoVersion") .IsRequired() .HasMaxLength(20) @@ -1033,6 +1876,82 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("MongoDatabases"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChannelId") + .HasColumnType("uniqueidentifier"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IncidentId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFiring") + .HasColumnType("bit"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Property("Id") @@ -1078,6 +1997,310 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("OpenStackConnections"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Replicas") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterSize") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("PersistenceEnabled") + .HasColumnType("bit"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageLinkId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("nvarchar(63)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("nvarchar(253)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Property("Id") @@ -1106,6 +2329,42 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("SecretVaults"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AppId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("MeasurementWindowDays") + .HasColumnType("int"); + + b.Property("TargetPercent") + .HasColumnType("float"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.Property("Id") @@ -1280,6 +2539,9 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("AppId") .HasColumnType("uniqueidentifier"); + b.Property("CnpgClusterId") + .HasColumnType("uniqueidentifier"); + b.Property("CnpgDatabaseId") .HasColumnType("uniqueidentifier"); @@ -1293,6 +2555,12 @@ namespace EntKube.Web.Data.Migrations.SqlServer .IsRequired() .HasColumnType("varbinary(max)"); + b.Property("GitRepositoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("KubernetesClusterId") + .HasColumnType("uniqueidentifier"); + b.Property("KubernetesNamespace") .HasMaxLength(63) .HasColumnType("nvarchar(63)"); @@ -1319,6 +2587,15 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("OpenStackConnectionId") .HasColumnType("uniqueidentifier"); + b.Property("RabbitMQClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedisClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("uniqueidentifier"); + b.Property("StorageLinkId") .HasColumnType("uniqueidentifier"); @@ -1331,20 +2608,37 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Property("VaultId") .HasColumnType("uniqueidentifier"); + b.Property("VpnRemoteEndpointId") + .HasColumnType("uniqueidentifier"); + b.HasKey("Id"); b.HasIndex("AppId"); + b.HasIndex("CnpgClusterId"); + b.HasIndex("CnpgDatabaseId"); b.HasIndex("ComponentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + b.HasIndex("MongoClusterId"); b.HasIndex("MongoDatabaseId"); + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("StorageLinkId"); + b.HasIndex("VpnRemoteEndpointId"); + b.HasIndex("VaultId", "AppId", "Name") .IsUnique(); @@ -1354,6 +2648,175 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("VaultSecrets"); }); + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClusterId") + .HasColumnType("uniqueidentifier"); + + b.Property("ComponentId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastSeenAt") + .HasColumnType("datetime2"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("VpnTunnelId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChildLifetime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DpdDelay") + .HasColumnType("int"); + + b.Property("DpdTimeout") + .HasColumnType("int"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeLifetime") + .HasColumnType("int"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IkeVersion") + .HasColumnType("int"); + + b.Property("LastStatusCheckAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -1487,6 +2950,17 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.HasOne("EntKube.Web.Data.Customer", "Customer") @@ -1518,11 +2992,25 @@ namespace EntKube.Web.Data.Migrations.SqlServer .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); b.Navigation("Cluster"); b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); }); modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => @@ -1544,6 +3032,79 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Environment"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") @@ -1651,11 +3212,29 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("AppDeployment"); b.Navigation("CnpgDatabase"); b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); }); modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => @@ -1687,6 +3266,31 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("ParentResource"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1709,6 +3313,43 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Component"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1739,26 +3380,7 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("User"); }); - modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => - { - b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") - .WithMany("Backups") - .HasForeignKey("KeycloakRealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") - .WithMany() - .HasForeignKey("StorageLinkId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Realm"); - - b.Navigation("StorageLink"); - }); - - modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => { b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") .WithMany() @@ -1771,6 +3393,11 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasForeignKey("CnpgDatabaseId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") .WithMany() .HasForeignKey("TenantId") @@ -1781,6 +3408,95 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("CnpgDatabase"); + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("Tenant"); }); @@ -1792,6 +3508,11 @@ namespace EntKube.Web.Data.Migrations.SqlServer .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.App", "LinkedApp") .WithMany() .HasForeignKey("LinkedAppId") @@ -1800,6 +3521,19 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("ComponentConfig"); b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); }); modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => @@ -1821,6 +3555,43 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") @@ -1869,6 +3640,36 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("MongoCluster"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1880,6 +3681,118 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1891,6 +3804,31 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") @@ -1992,6 +3930,11 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasForeignKey("AppId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") .WithMany() .HasForeignKey("CnpgDatabaseId") @@ -2002,6 +3945,16 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasForeignKey("ComponentId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") .WithMany() .HasForeignKey("MongoClusterId") @@ -2012,6 +3965,21 @@ namespace EntKube.Web.Data.Migrations.SqlServer .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") .WithMany() .HasForeignKey("StorageLinkId"); @@ -2022,19 +3990,86 @@ namespace EntKube.Web.Data.Migrations.SqlServer .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); + b.Navigation("CnpgCluster"); + b.Navigation("CnpgDatabase"); b.Navigation("Component"); + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + b.Navigation("MongoCluster"); b.Navigation("MongoDatabase"); + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("StorageLink"); b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -2088,17 +4123,34 @@ namespace EntKube.Web.Data.Migrations.SqlServer .IsRequired(); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Navigation("AppEnvironments"); b.Navigation("Deployments"); + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + b.Navigation("Secrets"); }); modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + b.Navigation("DatabaseBindings"); b.Navigation("Manifests"); @@ -2108,6 +4160,11 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("StorageBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Navigation("ExternalRoutes"); @@ -2146,6 +4203,13 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Navigation("Memberships"); @@ -2161,6 +4225,11 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Backups"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Navigation("Components"); @@ -2178,11 +4247,31 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Navigation("StorageLinks"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Navigation("Secrets"); @@ -2199,6 +4288,10 @@ namespace EntKube.Web.Data.Migrations.SqlServer b.Navigation("Environments"); + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + b.Navigation("Groups"); b.Navigation("KubernetesClusters"); @@ -2214,6 +4307,18 @@ namespace EntKube.Web.Data.Migrations.SqlServer { b.Navigation("Memberships"); }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); #pragma warning restore 612, 618 } } diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.Designer.cs new file mode 100644 index 0000000..190f6d1 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.Designer.cs @@ -0,0 +1,2215 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260521201841_AddExternalKeycloak")] + partial class AddExternalKeycloak + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.cs new file mode 100644 index 0000000..16c9f6f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260521201841_AddExternalKeycloak.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddExternalKeycloak : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "TEXT", + nullable: true, + oldClrType: typeof(Guid), + oldType: "TEXT"); + + migrationBuilder.AddColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs", + type: "TEXT", + maxLength: 200, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DisplayName", + table: "KeycloakComponentConfigs"); + + migrationBuilder.AlterColumn( + name: "ClusterComponentId", + table: "KeycloakComponentConfigs", + type: "TEXT", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "TEXT", + oldNullable: true); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.Designer.cs new file mode 100644 index 0000000..d80ad28 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.Designer.cs @@ -0,0 +1,2279 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260522070001_AddKeycloakTheme")] + partial class AddKeycloakTheme + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.cs new file mode 100644 index 0000000..aef629b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522070001_AddKeycloakTheme.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddKeycloakTheme : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "KeycloakThemes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + KeycloakComponentConfigId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + LoginTheme = table.Column(type: "TEXT", maxLength: 100, nullable: true), + AccountTheme = table.Column(type: "TEXT", maxLength: 100, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KeycloakThemes", x => x.Id); + table.ForeignKey( + name: "FK_KeycloakThemes_KeycloakComponentConfigs_KeycloakComponentConfigId", + column: x => x.KeycloakComponentConfigId, + principalTable: "KeycloakComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakThemes_KeycloakComponentConfigId_Name", + table: "KeycloakThemes", + columns: new[] { "KeycloakComponentConfigId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms", + column: "KeycloakThemeId", + principalTable: "KeycloakThemes", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakRealms_KeycloakThemes_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropTable( + name: "KeycloakThemes"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakRealms_KeycloakThemeId", + table: "KeycloakRealms"); + + migrationBuilder.DropColumn( + name: "KeycloakThemeId", + table: "KeycloakRealms"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.Designer.cs new file mode 100644 index 0000000..506fc2e --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.Designer.cs @@ -0,0 +1,2445 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260522091240_AddRegisteredPostgres")] + partial class AddRegisteredPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.cs new file mode 100644 index 0000000..65452ad --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522091240_AddRegisteredPostgres.cs @@ -0,0 +1,190 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddRegisteredPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresInstances", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + KubernetesClusterId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Namespace = table.Column(type: "TEXT", maxLength: 63, nullable: false), + ServiceName = table.Column(type: "TEXT", maxLength: 253, nullable: false), + Port = table.Column(type: "INTEGER", nullable: false), + AdminPodName = table.Column(type: "TEXT", maxLength: 253, nullable: false), + AdminUsername = table.Column(type: "TEXT", maxLength: 63, nullable: false), + Notes = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresInstances", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresInstances_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RegisteredPostgresDatabases", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RegisteredPostgresInstanceId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 63, nullable: false), + Owner = table.Column(type: "TEXT", maxLength: 63, nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDatabases", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDatabases_RegisteredPostgresInstances_RegisteredPostgresInstanceId", + column: x => x.RegisteredPostgresInstanceId, + principalTable: "RegisteredPostgresInstances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDatabases_RegisteredPostgresInstanceId_Name", + table: "RegisteredPostgresDatabases", + columns: new[] { "RegisteredPostgresInstanceId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_KubernetesClusterId", + table: "RegisteredPostgresInstances", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresInstances_TenantId_Name", + table: "RegisteredPostgresInstances", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "DatabaseBindings", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "VaultSecrets", + column: "RegisteredPostgresDatabaseId", + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DatabaseBindings_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + + migrationBuilder.DropForeignKey( + name: "FK_KeycloakComponentConfigs_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresDatabases"); + + migrationBuilder.DropTable( + name: "RegisteredPostgresInstances"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_KeycloakComponentConfigs_RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropIndex( + name: "IX_DatabaseBindings_RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "KeycloakComponentConfigs"); + + migrationBuilder.DropColumn( + name: "RegisteredPostgresDatabaseId", + table: "DatabaseBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.Designer.cs new file mode 100644 index 0000000..b70f214 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.Designer.cs @@ -0,0 +1,2496 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260522110446_AddRegisteredPostgresDump")] + partial class AddRegisteredPostgresDump + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.cs new file mode 100644 index 0000000..63348bb --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260522110446_AddRegisteredPostgresDump.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddRegisteredPostgresDump : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RegisteredPostgresDumps", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RegisteredPostgresDatabaseId = table.Column(type: "TEXT", nullable: false), + StorageLinkId = table.Column(type: "TEXT", nullable: false), + S3Key = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + SizeBytes = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RegisteredPostgresDumps", x => x.Id); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_RegisteredPostgresDatabases_RegisteredPostgresDatabaseId", + column: x => x.RegisteredPostgresDatabaseId, + principalTable: "RegisteredPostgresDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RegisteredPostgresDumps_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_RegisteredPostgresDatabaseId", + table: "RegisteredPostgresDumps", + column: "RegisteredPostgresDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_RegisteredPostgresDumps_StorageLinkId", + table: "RegisteredPostgresDumps", + column: "StorageLinkId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RegisteredPostgresDumps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.Designer.cs new file mode 100644 index 0000000..e6aa4dc --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.Designer.cs @@ -0,0 +1,2508 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260524194129_AddCnpgClusterVaultSecret")] + partial class AddCnpgClusterVaultSecret + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.cs new file mode 100644 index 0000000..21b1198 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260524194129_AddCnpgClusterVaultSecret.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddCnpgClusterVaultSecret : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CnpgClusterId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets", + column: "CnpgClusterId", + principalTable: "CnpgClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_CnpgClusters_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_CnpgClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "CnpgClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.Designer.cs new file mode 100644 index 0000000..84b829d --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.Designer.cs @@ -0,0 +1,2507 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260525202958_KeycloakBackupStorageLinkNullable")] + partial class KeycloakBackupStorageLinkNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.cs new file mode 100644 index 0000000..26affc8 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260525202958_KeycloakBackupStorageLinkNullable.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class KeycloakBackupStorageLinkNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "TEXT", + nullable: true, + oldClrType: typeof(Guid), + oldType: "TEXT"); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups"); + + migrationBuilder.AlterColumn( + name: "StorageLinkId", + table: "KeycloakBackups", + type: "TEXT", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "TEXT", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_KeycloakBackups_StorageLinks_StorageLinkId", + table: "KeycloakBackups", + column: "StorageLinkId", + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.Designer.cs new file mode 100644 index 0000000..abecb42 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.Designer.cs @@ -0,0 +1,2583 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260526053758_AddHarborComponentConfig")] + partial class AddHarborComponentConfig + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.cs new file mode 100644 index 0000000..3102730 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526053758_AddHarborComponentConfig.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddHarborComponentConfig : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborComponentConfigs", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + ClusterComponentId = table.Column(type: "TEXT", nullable: false), + CnpgDatabaseId = table.Column(type: "TEXT", nullable: true), + StorageLinkId = table.Column(type: "TEXT", nullable: true), + AdminUsername = table.Column(type: "TEXT", maxLength: 100, nullable: false), + RegistryUrl = table.Column(type: "TEXT", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborComponentConfigs", x => x.Id); + table.ForeignKey( + name: "FK_HarborComponentConfigs_ClusterComponents_ClusterComponentId", + column: x => x.ClusterComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborComponentConfigs_CnpgDatabases_CnpgDatabaseId", + column: x => x.CnpgDatabaseId, + principalTable: "CnpgDatabases", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborComponentConfigs_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_ClusterComponentId", + table: "HarborComponentConfigs", + column: "ClusterComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_CnpgDatabaseId", + table: "HarborComponentConfigs", + column: "CnpgDatabaseId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_StorageLinkId", + table: "HarborComponentConfigs", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborComponentConfigs_TenantId", + table: "HarborComponentConfigs", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborComponentConfigs"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.Designer.cs new file mode 100644 index 0000000..177fd63 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.Designer.cs @@ -0,0 +1,2587 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260526113526_AddCnpgMaxBackups")] + partial class AddCnpgMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.cs new file mode 100644 index 0000000..c7b81e6 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526113526_AddCnpgMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddCnpgMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "CnpgClusters", + type: "INTEGER", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..3d88997 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.Designer.cs @@ -0,0 +1,2586 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260526114833_FixCnpgMaxBackupsDefault")] + partial class FixCnpgMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.cs new file mode 100644 index 0000000..198c8d8 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526114833_FixCnpgMaxBackupsDefault.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class FixCnpgMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Backfill rows that got MaxBackups=0 from the initial migration which + // was generated with defaultValue:0 instead of defaultValue:20. + migrationBuilder.Sql( + "UPDATE \"CnpgClusters\" SET \"MaxBackups\" = 20 WHERE \"MaxBackups\" = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Data migrations cannot be cleanly reversed. + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.Designer.cs new file mode 100644 index 0000000..90ec1f2 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.Designer.cs @@ -0,0 +1,2590 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260526121543_AddMongoMaxBackups")] + partial class AddMongoMaxBackups + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .HasDefaultValue(20) + .HasColumnType("INTEGER"); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.cs new file mode 100644 index 0000000..be189e7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121543_AddMongoMaxBackups.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMongoMaxBackups : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxBackups", + table: "MongoClusters", + type: "INTEGER", + nullable: false, + defaultValue: 20); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxBackups", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.Designer.cs new file mode 100644 index 0000000..f3924bd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.Designer.cs @@ -0,0 +1,2593 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260526121829_FixMongoMaxBackupsDefault")] + partial class FixMongoMaxBackupsDefault + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.cs new file mode 100644 index 0000000..178e7b6 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260526121829_FixMongoMaxBackupsDefault.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class FixMongoMaxBackupsDefault : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + "UPDATE \"MongoClusters\" SET \"MaxBackups\" = 20 WHERE \"MaxBackups\" = 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.Designer.cs new file mode 100644 index 0000000..aa9fab5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.Designer.cs @@ -0,0 +1,2688 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260527143402_AddDockerRegistryCredential")] + partial class AddDockerRegistryCredential + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.cs new file mode 100644 index 0000000..68e933d --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260527143402_AddDockerRegistryCredential.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddDockerRegistryCredential : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DockerRegistryCredentials", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + VaultId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + RegistryType = table.Column(type: "INTEGER", nullable: false), + Server = table.Column(type: "TEXT", maxLength: 500, nullable: false), + Username = table.Column(type: "TEXT", maxLength: 300, nullable: false), + EncryptedPassword = table.Column(type: "BLOB", nullable: false), + PasswordNonce = table.Column(type: "BLOB", nullable: false), + Email = table.Column(type: "TEXT", maxLength: 254, nullable: true), + AppId = table.Column(type: "TEXT", nullable: true), + KubernetesClusterId = table.Column(type: "TEXT", nullable: true), + KubernetesSecretName = table.Column(type: "TEXT", maxLength: 253, nullable: true), + KubernetesNamespace = table.Column(type: "TEXT", maxLength: 63, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DockerRegistryCredentials", x => x.Id); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_DockerRegistryCredentials_SecretVaults_VaultId", + column: x => x.VaultId, + principalTable: "SecretVaults", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_AppId", + table: "DockerRegistryCredentials", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_KubernetesClusterId", + table: "DockerRegistryCredentials", + column: "KubernetesClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_DockerRegistryCredentials_VaultId", + table: "DockerRegistryCredentials", + column: "VaultId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DockerRegistryCredentials"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.Designer.cs new file mode 100644 index 0000000..cabaf4f --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.Designer.cs @@ -0,0 +1,2700 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260527151539_AddAppSecretClusterId")] + partial class AddAppSecretClusterId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.cs new file mode 100644 index 0000000..305db4e --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260527151539_AddAppSecretClusterId.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddAppSecretClusterId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KubernetesClusterId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets", + column: "KubernetesClusterId", + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_KubernetesClusters_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_KubernetesClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "KubernetesClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.Designer.cs new file mode 100644 index 0000000..7665718 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.Designer.cs @@ -0,0 +1,2762 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260528095213_AddAuditEventsAndRouteHealth")] + partial class AddAuditEventsAndRouteHealth + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.cs new file mode 100644 index 0000000..7a601f7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260528095213_AddAuditEventsAndRouteHealth.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddAuditEventsAndRouteHealth : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsReachable", + table: "ExternalRoutes", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastStatusCode", + table: "ExternalRoutes", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateTable( + name: "AuditEvents", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + DeploymentId = table.Column(type: "TEXT", nullable: true), + PerformedBy = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Action = table.Column(type: "TEXT", maxLength: 100, nullable: false), + ResourceKind = table.Column(type: "TEXT", maxLength: 100, nullable: false), + ResourceName = table.Column(type: "TEXT", maxLength: 253, nullable: true), + Details = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + OccurredAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuditEvents", x => x.Id); + table.ForeignKey( + name: "FK_AuditEvents_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_DeploymentId", + table: "AuditEvents", + column: "DeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_AuditEvents_OccurredAt", + table: "AuditEvents", + column: "OccurredAt"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuditEvents"); + + migrationBuilder.DropColumn( + name: "IsReachable", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastHealthCheckAt", + table: "ExternalRoutes"); + + migrationBuilder.DropColumn( + name: "LastStatusCode", + table: "ExternalRoutes"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.Designer.cs new file mode 100644 index 0000000..2b3a929 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.Designer.cs @@ -0,0 +1,2823 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601071027_AddHarborProject")] + partial class AddHarborProject + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.cs new file mode 100644 index 0000000..b10d827 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601071027_AddHarborProject.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddHarborProject : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "HarborProjects", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + HarborComponentConfigId = table.Column(type: "TEXT", nullable: false), + ProjectName = table.Column(type: "TEXT", maxLength: 255, nullable: false), + LinkedAppId = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HarborProjects", x => x.Id); + table.ForeignKey( + name: "FK_HarborProjects_Apps_LinkedAppId", + column: x => x.LinkedAppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_HarborProjects_HarborComponentConfigs_HarborComponentConfigId", + column: x => x.HarborComponentConfigId, + principalTable: "HarborComponentConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_HarborProjects_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_HarborComponentConfigId_ProjectName", + table: "HarborProjects", + columns: new[] { "HarborComponentConfigId", "ProjectName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_LinkedAppId", + table: "HarborProjects", + column: "LinkedAppId"); + + migrationBuilder.CreateIndex( + name: "IX_HarborProjects_TenantId", + table: "HarborProjects", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HarborProjects"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.Designer.cs new file mode 100644 index 0000000..7b56cc4 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.Designer.cs @@ -0,0 +1,2993 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601124736_AddRabbitMQ")] + partial class AddRabbitMQ + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.cs new file mode 100644 index 0000000..820cf02 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601124736_AddRabbitMQ.cs @@ -0,0 +1,127 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddRabbitMQ : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RabbitMQClusters", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + KubernetesClusterId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 63, nullable: false), + Namespace = table.Column(type: "TEXT", maxLength: 63, nullable: false), + RabbitMQVersion = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Replicas = table.Column(type: "INTEGER", nullable: false), + StorageSize = table.Column(type: "TEXT", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "TEXT", maxLength: 63, nullable: true), + StorageLinkId = table.Column(type: "TEXT", nullable: true), + BackupSchedule = table.Column(type: "TEXT", maxLength: 100, nullable: true), + MaxBackups = table.Column(type: "INTEGER", nullable: false, defaultValue: 10), + Status = table.Column(type: "INTEGER", nullable: false), + LastError = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQClusters", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQClusters_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_RabbitMQClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RabbitMQBackups", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RabbitMQClusterId = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + StorageLinkId = table.Column(type: "TEXT", nullable: true), + ObjectKey = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + ClusterName = table.Column(type: "TEXT", maxLength: 63, nullable: false), + SizeBytes = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + LastError = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + CompletedAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RabbitMQBackups", x => x.Id); + table.ForeignKey( + name: "FK_RabbitMQBackups_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RabbitMQBackups_StorageLinks_StorageLinkId", + column: x => x.StorageLinkId, + principalTable: "StorageLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_RabbitMQClusterId", + table: "RabbitMQBackups", + column: "RabbitMQClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQBackups_StorageLinkId", + table: "RabbitMQBackups", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_KubernetesClusterId_Name_Namespace", + table: "RabbitMQClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_StorageLinkId", + table: "RabbitMQClusters", + column: "StorageLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_RabbitMQClusters_TenantId", + table: "RabbitMQClusters", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RabbitMQBackups"); + + migrationBuilder.DropTable( + name: "RabbitMQClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.Designer.cs new file mode 100644 index 0000000..521512a --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.Designer.cs @@ -0,0 +1,3005 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601132351_AddRabbitMQCredentialVaultSync")] + partial class AddRabbitMQCredentialVaultSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.cs new file mode 100644 index 0000000..6a5384b --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601132351_AddRabbitMQCredentialVaultSync.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddRabbitMQCredentialVaultSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets", + column: "RabbitMQClusterId", + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RabbitMQClusters_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RabbitMQClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RabbitMQClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.Designer.cs new file mode 100644 index 0000000..23442cd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.Designer.cs @@ -0,0 +1,3008 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601160410_AddMongoClusterIsExternal")] + partial class AddMongoClusterIsExternal + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.cs new file mode 100644 index 0000000..1ee0ab5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601160410_AddMongoClusterIsExternal.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMongoClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "MongoClusters", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.Designer.cs new file mode 100644 index 0000000..7b5c501 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.Designer.cs @@ -0,0 +1,3300 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601163342_AddMonitoringAndAlerting")] + partial class AddMonitoringAndAlerting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.cs new file mode 100644 index 0000000..6a0aaef --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601163342_AddMonitoringAndAlerting.cs @@ -0,0 +1,205 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMonitoringAndAlerting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AlertIncidents", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ClusterId = table.Column(type: "TEXT", nullable: false), + Fingerprint = table.Column(type: "TEXT", maxLength: 100, nullable: false), + AlertName = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Severity = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Summary = table.Column(type: "TEXT", maxLength: 500, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + LabelsJson = table.Column(type: "TEXT", nullable: false), + StartsAt = table.Column(type: "TEXT", nullable: false), + EndsAt = table.Column(type: "TEXT", nullable: true), + Status = table.Column(type: "TEXT", maxLength: 20, nullable: false), + AcknowledgedBy = table.Column(type: "TEXT", maxLength: 256, nullable: true), + AcknowledgedAt = table.Column(type: "TEXT", nullable: true), + ResolvedAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AlertIncidents", x => x.Id); + table.ForeignKey( + name: "FK_AlertIncidents_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DeploymentHealthSnapshots", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + DeploymentId = table.Column(type: "TEXT", nullable: false), + HealthStatus = table.Column(type: "TEXT", maxLength: 20, nullable: false), + SyncStatus = table.Column(type: "TEXT", maxLength: 20, nullable: false), + ReadyReplicas = table.Column(type: "INTEGER", nullable: true), + TotalReplicas = table.Column(type: "INTEGER", nullable: true), + SnapshotAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeploymentHealthSnapshots", x => x.Id); + table.ForeignKey( + name: "FK_DeploymentHealthSnapshots_AppDeployments_DeploymentId", + column: x => x.DeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationChannels", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Type = table.Column(type: "TEXT", maxLength: 20, nullable: false), + ConfigurationJson = table.Column(type: "TEXT", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + SeverityFilter = table.Column(type: "TEXT", maxLength: 30, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationChannels", x => x.Id); + table.ForeignKey( + name: "FK_NotificationChannels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IncidentNotes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + IncidentId = table.Column(type: "TEXT", nullable: false), + Author = table.Column(type: "TEXT", maxLength: 256, nullable: false), + Content = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IncidentNotes", x => x.Id); + table.ForeignKey( + name: "FK_IncidentNotes_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NotificationDeliveries", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + IncidentId = table.Column(type: "TEXT", nullable: false), + ChannelId = table.Column(type: "TEXT", nullable: false), + IsFiring = table.Column(type: "INTEGER", nullable: false), + SentAt = table.Column(type: "TEXT", nullable: false), + Success = table.Column(type: "INTEGER", nullable: false), + Error = table.Column(type: "TEXT", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationDeliveries", x => x.Id); + table.ForeignKey( + name: "FK_NotificationDeliveries_AlertIncidents_IncidentId", + column: x => x.IncidentId, + principalTable: "AlertIncidents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_NotificationDeliveries_NotificationChannels_ChannelId", + column: x => x.ChannelId, + principalTable: "NotificationChannels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_ClusterId_Fingerprint", + table: "AlertIncidents", + columns: new[] { "ClusterId", "Fingerprint" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_StartsAt", + table: "AlertIncidents", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_AlertIncidents_Status", + table: "AlertIncidents", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_DeploymentHealthSnapshots_DeploymentId_SnapshotAt", + table: "DeploymentHealthSnapshots", + columns: new[] { "DeploymentId", "SnapshotAt" }); + + migrationBuilder.CreateIndex( + name: "IX_IncidentNotes_IncidentId", + table: "IncidentNotes", + column: "IncidentId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationChannels_TenantId_Name", + table: "NotificationChannels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_ChannelId", + table: "NotificationDeliveries", + column: "ChannelId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationDeliveries_IncidentId_ChannelId_IsFiring", + table: "NotificationDeliveries", + columns: new[] { "IncidentId", "ChannelId", "IsFiring" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeploymentHealthSnapshots"); + + migrationBuilder.DropTable( + name: "IncidentNotes"); + + migrationBuilder.DropTable( + name: "NotificationDeliveries"); + + migrationBuilder.DropTable( + name: "AlertIncidents"); + + migrationBuilder.DropTable( + name: "NotificationChannels"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs new file mode 100644 index 0000000..8a9ac9a --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.Designer.cs @@ -0,0 +1,3464 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory")] + partial class AddMaintenanceWindowsSlaTargetsRouteHistory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.cs new file mode 100644 index 0000000..d4c9362 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601181706_AddMaintenanceWindowsSlaTargetsRouteHistory.cs @@ -0,0 +1,152 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMaintenanceWindowsSlaTargetsRouteHistory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ExternalRouteHealthHistories", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RouteId = table.Column(type: "TEXT", nullable: false), + IsReachable = table.Column(type: "INTEGER", nullable: false), + StatusCode = table.Column(type: "INTEGER", nullable: true), + ResponseMs = table.Column(type: "INTEGER", nullable: true), + CheckedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExternalRouteHealthHistories", x => x.Id); + table.ForeignKey( + name: "FK_ExternalRouteHealthHistories_ExternalRoutes_RouteId", + column: x => x.RouteId, + principalTable: "ExternalRoutes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MaintenanceWindows", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + ClusterId = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + StartsAt = table.Column(type: "TEXT", nullable: false), + EndsAt = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceWindows", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceWindows_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_MaintenanceWindows_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SlaTargets", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + CustomerId = table.Column(type: "TEXT", nullable: true), + AppId = table.Column(type: "TEXT", nullable: true), + TargetPercent = table.Column(type: "REAL", nullable: false), + MeasurementWindowDays = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SlaTargets", x => x.Id); + table.ForeignKey( + name: "FK_SlaTargets_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SlaTargets_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExternalRouteHealthHistories_RouteId_CheckedAt", + table: "ExternalRouteHealthHistories", + columns: new[] { "RouteId", "CheckedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_ClusterId", + table: "MaintenanceWindows", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_StartsAt", + table: "MaintenanceWindows", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceWindows_TenantId_StartsAt", + table: "MaintenanceWindows", + columns: new[] { "TenantId", "StartsAt" }); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_AppId", + table: "SlaTargets", + column: "AppId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_CustomerId", + table: "SlaTargets", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_SlaTargets_TenantId_CustomerId_AppId", + table: "SlaTargets", + columns: new[] { "TenantId", "CustomerId", "AppId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ExternalRouteHealthHistories"); + + migrationBuilder.DropTable( + name: "MaintenanceWindows"); + + migrationBuilder.DropTable( + name: "SlaTargets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.Designer.cs new file mode 100644 index 0000000..9f44872 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.Designer.cs @@ -0,0 +1,3534 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601190020_AddMessagingBinding")] + partial class AddMessagingBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.cs new file mode 100644 index 0000000..c3b84da --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601190020_AddMessagingBinding.cs @@ -0,0 +1,65 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMessagingBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MessagingBindings", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RabbitMQClusterId = table.Column(type: "TEXT", nullable: false), + AppDeploymentId = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + Vhost = table.Column(type: "TEXT", maxLength: 200, nullable: false), + QueueName = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ExchangeName = table.Column(type: "TEXT", maxLength: 255, nullable: true), + KubernetesSecretName = table.Column(type: "TEXT", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "INTEGER", nullable: false), + LastSyncedAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MessagingBindings", x => x.Id); + table.ForeignKey( + name: "FK_MessagingBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MessagingBindings_RabbitMQClusters_RabbitMQClusterId", + column: x => x.RabbitMQClusterId, + principalTable: "RabbitMQClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_AppDeploymentId", + table: "MessagingBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_MessagingBindings_RabbitMQClusterId", + table: "MessagingBindings", + column: "RabbitMQClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MessagingBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.Designer.cs new file mode 100644 index 0000000..49e2654 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.Designer.cs @@ -0,0 +1,3537 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260601200000_AddMongoClusterExternalUri")] + partial class AddMongoClusterExternalUri + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.cs new file mode 100644 index 0000000..2194cbd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260601200000_AddMongoClusterExternalUri.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddMongoClusterExternalUri : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExternalUri", + table: "MongoClusters", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExternalUri", + table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.Designer.cs new file mode 100644 index 0000000..225cfe8 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.Designer.cs @@ -0,0 +1,3764 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260602091223_AddVpnTunnels")] + partial class AddVpnTunnels + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.cs new file mode 100644 index 0000000..6540924 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602091223_AddVpnTunnels.cs @@ -0,0 +1,176 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddVpnTunnels : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "VpnTunnels", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", nullable: true), + TunnelType = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Status = table.Column(type: "TEXT", maxLength: 20, nullable: false), + IkeVersion = table.Column(type: "INTEGER", nullable: false), + IkeProposal = table.Column(type: "TEXT", maxLength: 100, nullable: false), + EspProposal = table.Column(type: "TEXT", maxLength: 100, nullable: false), + DpdDelay = table.Column(type: "INTEGER", nullable: false), + DpdTimeout = table.Column(type: "INTEGER", nullable: false), + LastStatusCheckAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnTunnels", x => x.Id); + table.ForeignKey( + name: "FK_VpnTunnels_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnLocalEndpoints", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + VpnTunnelId = table.Column(type: "TEXT", nullable: false), + ClusterId = table.Column(type: "TEXT", nullable: false), + ComponentId = table.Column(type: "TEXT", nullable: true), + Subnets = table.Column(type: "TEXT", maxLength: 1000, nullable: false), + PublicIp = table.Column(type: "TEXT", maxLength: 45, nullable: true), + Role = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Status = table.Column(type: "TEXT", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnLocalEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_ClusterComponents_ComponentId", + column: x => x.ComponentId, + principalTable: "ClusterComponents", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_KubernetesClusters_ClusterId", + column: x => x.ClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_VpnLocalEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VpnRemoteEndpoints", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + VpnTunnelId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + PublicIp = table.Column(type: "TEXT", maxLength: 45, nullable: false), + Subnets = table.Column(type: "TEXT", maxLength: 1000, nullable: false), + AuthMode = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Status = table.Column(type: "TEXT", maxLength: 20, nullable: false), + LastSeenAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VpnRemoteEndpoints", x => x.Id); + table.ForeignKey( + name: "FK_VpnRemoteEndpoints_VpnTunnels_VpnTunnelId", + column: x => x.VpnTunnelId, + principalTable: "VpnTunnels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ClusterId", + table: "VpnLocalEndpoints", + column: "ClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_ComponentId", + table: "VpnLocalEndpoints", + column: "ComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnLocalEndpoints_VpnTunnelId", + table: "VpnLocalEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnRemoteEndpoints_VpnTunnelId", + table: "VpnRemoteEndpoints", + column: "VpnTunnelId"); + + migrationBuilder.CreateIndex( + name: "IX_VpnTunnels_TenantId_Name", + table: "VpnTunnels", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets", + column: "VpnRemoteEndpointId", + principalTable: "VpnRemoteEndpoints", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_VpnRemoteEndpoints_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "VpnLocalEndpoints"); + + migrationBuilder.DropTable( + name: "VpnRemoteEndpoints"); + + migrationBuilder.DropTable( + name: "VpnTunnels"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_VpnRemoteEndpointId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "VpnRemoteEndpointId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.Designer.cs new file mode 100644 index 0000000..6fe2639 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.Designer.cs @@ -0,0 +1,3778 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260602103442_AddVpnLifetimeAndIdentity")] + partial class AddVpnLifetimeAndIdentity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.cs new file mode 100644 index 0000000..21dc89d --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602103442_AddVpnLifetimeAndIdentity.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddVpnLifetimeAndIdentity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ChildLifetime", + table: "VpnTunnels", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "IkeLifetime", + table: "VpnTunnels", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "LocalId", + table: "VpnRemoteEndpoints", + type: "TEXT", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints", + type: "TEXT", + maxLength: 500, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ChildLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "IkeLifetime", + table: "VpnTunnels"); + + migrationBuilder.DropColumn( + name: "LocalId", + table: "VpnRemoteEndpoints"); + + migrationBuilder.DropColumn( + name: "RemoteId", + table: "VpnRemoteEndpoints"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.Designer.cs new file mode 100644 index 0000000..9996875 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.Designer.cs @@ -0,0 +1,3870 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260602124559_AddRedisCluster")] + partial class AddRedisCluster + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PersistenceEnabled") + .HasColumnType("INTEGER"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.cs new file mode 100644 index 0000000..dfa45fd --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602124559_AddRedisCluster.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddRedisCluster : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RedisClusterId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "RedisClusters", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + KubernetesClusterId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 63, nullable: false), + Namespace = table.Column(type: "TEXT", maxLength: 63, nullable: false), + ClusterSize = table.Column(type: "INTEGER", nullable: false), + RedisVersion = table.Column(type: "TEXT", maxLength: 20, nullable: false), + StorageSize = table.Column(type: "TEXT", maxLength: 20, nullable: false), + StorageClass = table.Column(type: "TEXT", maxLength: 63, nullable: true), + PersistenceEnabled = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + LastError = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RedisClusters", x => x.Id); + table.ForeignKey( + name: "FK_RedisClusters_KubernetesClusters_KubernetesClusterId", + column: x => x.KubernetesClusterId, + principalTable: "KubernetesClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RedisClusters_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId"); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_KubernetesClusterId_Name_Namespace", + table: "RedisClusters", + columns: new[] { "KubernetesClusterId", "Name", "Namespace" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RedisClusters_TenantId", + table: "RedisClusters", + column: "TenantId"); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets", + column: "RedisClusterId", + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_RedisClusters_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "RedisClusters"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_RedisClusterId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "RedisClusterId", + table: "VaultSecrets"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.Designer.cs new file mode 100644 index 0000000..47a5857 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.Designer.cs @@ -0,0 +1,3929 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260602185535_AddCacheBinding")] + partial class AddCacheBinding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PersistenceEnabled") + .HasColumnType("INTEGER"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.cs new file mode 100644 index 0000000..b565383 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602185535_AddCacheBinding.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddCacheBinding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CacheBindings", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RedisClusterId = table.Column(type: "TEXT", nullable: false), + AppDeploymentId = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + KubernetesSecretName = table.Column(type: "TEXT", maxLength: 253, nullable: false), + SyncEnabled = table.Column(type: "INTEGER", nullable: false), + LastSyncedAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CacheBindings", x => x.Id); + table.ForeignKey( + name: "FK_CacheBindings_AppDeployments_AppDeploymentId", + column: x => x.AppDeploymentId, + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CacheBindings_RedisClusters_RedisClusterId", + column: x => x.RedisClusterId, + principalTable: "RedisClusters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_AppDeploymentId", + table: "CacheBindings", + column: "AppDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_CacheBindings_RedisClusterId", + table: "CacheBindings", + column: "RedisClusterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CacheBindings"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260602200000_AddMongoClusterResources.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260602200000_AddMongoClusterResources.cs new file mode 100644 index 0000000..63e5809 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260602200000_AddMongoClusterResources.cs @@ -0,0 +1,51 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260602200000_AddMongoClusterResources")] + public partial class AddMongoClusterResources : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CpuRequest", + table: "MongoClusters", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "CpuLimit", + table: "MongoClusters", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryRequest", + table: "MongoClusters", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "MemoryLimit", + table: "MongoClusters", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "CpuRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "CpuLimit", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryRequest", table: "MongoClusters"); + migrationBuilder.DropColumn(name: "MemoryLimit", table: "MongoClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260603080000_AddCnpgClusterIsExternal.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260603080000_AddCnpgClusterIsExternal.cs new file mode 100644 index 0000000..dfa32b7 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260603080000_AddCnpgClusterIsExternal.cs @@ -0,0 +1,33 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260603080000_AddCnpgClusterIsExternal")] + public partial class AddCnpgClusterIsExternal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "CnpgClusters", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "CnpgClusters"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.Designer.cs new file mode 100644 index 0000000..1819ff5 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.Designer.cs @@ -0,0 +1,4126 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260603215310_AddGitSync")] + partial class AddGitSync + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("GitAutoSync") + .HasColumnType("INTEGER"); + + b.Property("GitLastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentDeploymentId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("SourceFile") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TrustedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CpuLimit") + .HasColumnType("TEXT"); + + b.Property("CpuRequest") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MemoryLimit") + .HasColumnType("TEXT"); + + b.Property("MemoryRequest") + .HasColumnType("TEXT"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PersistenceEnabled") + .HasColumnType("INTEGER"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.cs new file mode 100644 index 0000000..13989cb --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260603215310_AddGitSync.cs @@ -0,0 +1,251 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddGitSync : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "VaultSecrets", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "SourceFile", + table: "DeploymentManifests", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitAutoSync", + table: "AppDeployments", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "GitLastSyncedAt", + table: "AppDeployments", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments", + type: "TEXT", + maxLength: 40, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitPath", + table: "AppDeployments", + type: "TEXT", + maxLength: 500, + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRepositoryId", + table: "AppDeployments", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "GitRevision", + table: "AppDeployments", + type: "TEXT", + maxLength: 200, + nullable: true); + + migrationBuilder.AddColumn( + name: "ParentDeploymentId", + table: "AppDeployments", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "GitRepositories", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Url = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + AuthType = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Username = table.Column(type: "TEXT", maxLength: 300, nullable: true), + DefaultBranch = table.Column(type: "TEXT", maxLength: 200, nullable: false, defaultValue: "main"), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GitRepositories", x => x.Id); + table.ForeignKey( + name: "FK_GitRepositories_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GitKnownHosts", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: false), + Hostname = table.Column(type: "TEXT", maxLength: 253, nullable: false), + Fingerprint = table.Column(type: "TEXT", maxLength: 200, nullable: false), + KeyType = table.Column(type: "TEXT", maxLength: 50, nullable: false), + TrustedAt = table.Column(type: "TEXT", nullable: false), + GitRepositoryId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GitKnownHosts", x => x.Id); + table.ForeignKey( + name: "FK_GitKnownHosts_GitRepositories_GitRepositoryId", + column: x => x.GitRepositoryId, + principalTable: "GitRepositories", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_GitKnownHosts_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_GitRepositoryId", + table: "GitKnownHosts", + column: "GitRepositoryId"); + + migrationBuilder.CreateIndex( + name: "IX_GitKnownHosts_TenantId_Hostname", + table: "GitKnownHosts", + columns: new[] { "TenantId", "Hostname" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GitRepositories_TenantId_Name", + table: "GitRepositories", + columns: new[] { "TenantId", "Name" }, + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments", + column: "ParentDeploymentId", + principalTable: "AppDeployments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets", + column: "GitRepositoryId", + principalTable: "GitRepositories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_AppDeployments_GitRepositories_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropForeignKey( + name: "FK_VaultSecrets_GitRepositories_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropTable( + name: "GitKnownHosts"); + + migrationBuilder.DropTable( + name: "GitRepositories"); + + migrationBuilder.DropIndex( + name: "IX_VaultSecrets_GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropIndex( + name: "IX_AppDeployments_ParentDeploymentId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "VaultSecrets"); + + migrationBuilder.DropColumn( + name: "SourceFile", + table: "DeploymentManifests"); + + migrationBuilder.DropColumn( + name: "GitAutoSync", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedAt", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitLastSyncedCommit", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitPath", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRepositoryId", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "GitRevision", + table: "AppDeployments"); + + migrationBuilder.DropColumn( + name: "ParentDeploymentId", + table: "AppDeployments"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.Designer.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.Designer.cs new file mode 100644 index 0000000..cd6bd21 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.Designer.cs @@ -0,0 +1,4317 @@ +// +using System; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260604060604_AddAppGovernance")] + partial class AddAppGovernance + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "Name") + .IsUnique(); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("GitAutoSync") + .HasColumnType("INTEGER"); + + b.Property("GitLastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentDeploymentId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppDeployments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("LinkedAt") + .HasColumnType("TEXT"); + + b.HasKey("AppId", "EnvironmentId"); + + b.HasIndex("EnvironmentId"); + + b.ToTable("AppEnvironments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CustomYaml") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("MaxPods") + .HasColumnType("INTEGER"); + + b.Property("MaxPvcs") + .HasColumnType("INTEGER"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("AutoMountToken") + .HasColumnType("INTEGER"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("AppRbacPolicyId") + .HasColumnType("TEXT"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HelmChartName") + .HasColumnType("TEXT"); + + b.Property("HelmChartVersion") + .HasColumnType("TEXT"); + + b.Property("HelmRepoUrl") + .HasColumnType("TEXT"); + + b.Property("HelmValues") + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasColumnType("TEXT"); + + b.Property("ReleaseName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId", "Name") + .IsUnique(); + + b.ToTable("ClusterComponents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Instances") + .HasColumnType("INTEGER"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PostgresVersion") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("CnpgClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CnpgClusterId", "Name") + .IsUnique(); + + b.ToTable("CnpgDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("UserId", "CustomerId"); + + b.HasIndex("CustomerId"); + + b.ToTable("CustomerAccesses"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.ToTable("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("SourceFile") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("YamlContent") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.ToTable("DeploymentManifests"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("ParentResourceId") + .HasColumnType("TEXT"); + + b.Property("StatusMessage") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("ParentResourceId"); + + b.ToTable("DeploymentResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Environments"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterIssuerName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GatewayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GatewayNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + + b.Property("PathPrefix") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ServicePort") + .HasColumnType("INTEGER"); + + b.Property("TlsCertificate") + .HasColumnType("TEXT"); + + b.Property("TlsMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TlsPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.ToTable("ExternalRoutes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TrustedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakRealmId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakRealmId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("KeycloakBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("TenantId"); + + b.ToTable("KeycloakComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasColumnType("TEXT"); + + b.Property("RealmName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakThemeId"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("KeycloakComponentConfigId", "RealmName") + .IsUnique(); + + b.ToTable("KeycloakRealms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("ContextName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Kubeconfig") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CpuLimit") + .HasColumnType("TEXT"); + + b.Property("CpuRequest") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + + b.Property("Members") + .HasColumnType("INTEGER"); + + b.Property("MemoryLimit") + .HasColumnType("TEXT"); + + b.Property("MemoryRequest") + .HasColumnType("TEXT"); + + b.Property("MongoVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RetentionDays") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("MongoClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MongoClusterId", "Name") + .IsUnique(); + + b.ToTable("MongoDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectDomainName") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("UserDomainName") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("OpenStackConnections"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PersistenceEnabled") + .HasColumnType("INTEGER"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedDataKey") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("SecretVaults"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BucketName") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("EnvironmentId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Region") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ComponentId"); + + b.HasIndex("EnvironmentId"); + + b.HasIndex("OpenStackConnectionId"); + + b.HasIndex("TenantId"); + + b.ToTable("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "TenantId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantMemberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("TenantRoles"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EncryptedValue") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("MongoClusterId") + .HasColumnType("TEXT"); + + b.Property("MongoDatabaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OpenStackConnectionId") + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("SyncToKubernetes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CnpgClusterId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("MongoClusterId"); + + b.HasIndex("MongoDatabaseId"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("VpnRemoteEndpointId"); + + b.HasIndex("VaultId", "AppId", "Name") + .IsUnique(); + + b.HasIndex("VaultId", "ComponentId", "Name") + .IsUnique(); + + b.ToTable("VaultSecrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany("Apps") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Deployments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("Cluster"); + + b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("AppEnvironments") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("AppEnvironments") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Environment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany("Components") + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgBackup", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Backups") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany("Databases") + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CnpgCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CustomerAccess", b => + { + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DatabaseBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("DatabaseBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AppDeployment"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Manifests") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany("Resources") + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.DeploymentResource", "ParentResource") + .WithMany("ChildResources") + .HasForeignKey("ParentResourceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Deployment"); + + b.Navigation("ParentResource"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Environments") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRoute", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("ExternalRoutes") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Groups") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GroupMembership", b => + { + b.HasOne("EntKube.Web.Data.Group", "Group") + .WithMany("Memberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany("Realms") + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany("KubernetesClusters") + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("KubernetesClusters") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Environment"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Backups") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany("Databases") + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MongoCluster"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithOne("Vault") + .HasForeignKey("EntKube.Web.Data.SecretVault", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("StorageBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("StorageBindings") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany("StorageBindings") + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Component"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId"); + + b.HasOne("EntKube.Web.Data.Environment", "Environment") + .WithMany() + .HasForeignKey("EnvironmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.OpenStackConnection", "OpenStackConnection") + .WithMany("StorageLinks") + .HasForeignKey("OpenStackConnectionId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Component"); + + b.Navigation("Environment"); + + b.Navigation("OpenStackConnection"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantMembership", b => + { + b.HasOne("EntKube.Web.Data.TenantRole", "Role") + .WithMany("Memberships") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Memberships") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("Roles") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VaultSecret", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany("Secrets") + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") + .WithMany() + .HasForeignKey("MongoClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.MongoDatabase", "MongoDatabase") + .WithMany() + .HasForeignKey("MongoDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId"); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany("Secrets") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("App"); + + b.Navigation("CnpgCluster"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("Component"); + + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("MongoCluster"); + + b.Navigation("MongoDatabase"); + + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + + b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EntKube.Web.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("EntKube.Web.Data.App", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("Deployments"); + + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => + { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + + b.Navigation("DatabaseBindings"); + + b.Navigation("Manifests"); + + b.Navigation("Resources"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => + { + b.Navigation("ExternalRoutes"); + + b.Navigation("Secrets"); + + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CnpgDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Customer", b => + { + b.Navigation("Apps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentResource", b => + { + b.Navigation("ChildResources"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Environment", b => + { + b.Navigation("AppEnvironments"); + + b.Navigation("KubernetesClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Group", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakRealm", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoCluster", b => + { + b.Navigation("Backups"); + + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MongoDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => + { + b.Navigation("StorageLinks"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.StorageLink", b => + { + b.Navigation("StorageBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.Tenant", b => + { + b.Navigation("Customers"); + + b.Navigation("Environments"); + + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + + b.Navigation("Groups"); + + b.Navigation("KubernetesClusters"); + + b.Navigation("Memberships"); + + b.Navigation("Roles"); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("EntKube.Web.Data.TenantRole", b => + { + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.cs b/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.cs new file mode 100644 index 0000000..5cef164 --- /dev/null +++ b/src/EntKube.Web/Data/Migrations/Sqlite/20260604060604_AddAppGovernance.cs @@ -0,0 +1,153 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntKube.Web.Data.Migrations.Sqlite +{ + /// + public partial class AddAppGovernance : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Namespace", + table: "Apps", + type: "TEXT", + maxLength: 63, + nullable: true); + + migrationBuilder.CreateTable( + name: "AppNetworkPolicies", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AppId = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 63, nullable: false), + PolicyType = table.Column(type: "TEXT", maxLength: 30, nullable: false), + AllowFromNamespace = table.Column(type: "TEXT", maxLength: 63, nullable: true), + CustomYaml = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AppNetworkPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppNetworkPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppQuotas", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AppId = table.Column(type: "TEXT", nullable: false), + CpuRequest = table.Column(type: "TEXT", maxLength: 20, nullable: true), + CpuLimit = table.Column(type: "TEXT", maxLength: 20, nullable: true), + MemoryRequest = table.Column(type: "TEXT", maxLength: 20, nullable: true), + MemoryLimit = table.Column(type: "TEXT", maxLength: 20, nullable: true), + MaxPods = table.Column(type: "INTEGER", nullable: true), + MaxPvcs = table.Column(type: "INTEGER", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppQuotas", x => x.Id); + table.ForeignKey( + name: "FK_AppQuotas_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacPolicies", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AppId = table.Column(type: "TEXT", nullable: false), + ServiceAccountName = table.Column(type: "TEXT", maxLength: 63, nullable: false), + AutoMountToken = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacPolicies", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacPolicies_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AppRbacRules", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AppRbacPolicyId = table.Column(type: "TEXT", nullable: false), + ApiGroups = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Resources = table.Column(type: "TEXT", maxLength: 500, nullable: false), + Verbs = table.Column(type: "TEXT", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppRbacRules", x => x.Id); + table.ForeignKey( + name: "FK_AppRbacRules_AppRbacPolicies_AppRbacPolicyId", + column: x => x.AppRbacPolicyId, + principalTable: "AppRbacPolicies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AppNetworkPolicies_AppId_Name", + table: "AppNetworkPolicies", + columns: new[] { "AppId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppQuotas_AppId", + table: "AppQuotas", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacPolicies_AppId", + table: "AppRbacPolicies", + column: "AppId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AppRbacRules_AppRbacPolicyId", + table: "AppRbacRules", + column: "AppRbacPolicyId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AppNetworkPolicies"); + + migrationBuilder.DropTable( + name: "AppQuotas"); + + migrationBuilder.DropTable( + name: "AppRbacRules"); + + migrationBuilder.DropTable( + name: "AppRbacPolicies"); + + migrationBuilder.DropColumn( + name: "Namespace", + table: "Apps"); + } + } +} diff --git a/src/EntKube.Web/Data/Migrations/Sqlite/ApplicationDbContextModelSnapshot.cs b/src/EntKube.Web/Data/Migrations/Sqlite/ApplicationDbContextModelSnapshot.cs index 228e37e..44d0249 100644 --- a/src/EntKube.Web/Data/Migrations/Sqlite/ApplicationDbContextModelSnapshot.cs +++ b/src/EntKube.Web/Data/Migrations/Sqlite/ApplicationDbContextModelSnapshot.cs @@ -17,6 +17,83 @@ namespace EntKube.Web.Data.Migrations.Sqlite #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "10.0.8"); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AcknowledgedAt") + .HasColumnType("TEXT"); + + b.Property("AcknowledgedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("AlertName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LabelsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StartsAt"); + + b.HasIndex("Status"); + + b.HasIndex("ClusterId", "Fingerprint") + .IsUnique(); + + b.ToTable("AlertIncidents"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Property("Id") @@ -34,6 +111,10 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasMaxLength(200) .HasColumnType("TEXT"); + b.Property("Namespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("CustomerId", "Name") @@ -60,6 +141,27 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("EnvironmentId") .HasColumnType("TEXT"); + b.Property("GitAutoSync") + .HasColumnType("INTEGER"); + + b.Property("GitLastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("GitLastSyncedCommit") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("GitPath") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("GitRevision") + .HasMaxLength(200) + .HasColumnType("TEXT"); + b.Property("HealthStatus") .IsRequired() .HasMaxLength(20) @@ -93,6 +195,9 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasMaxLength(63) .HasColumnType("TEXT"); + b.Property("ParentDeploymentId") + .HasColumnType("TEXT"); + b.Property("StatusMessage") .HasColumnType("TEXT"); @@ -112,6 +217,10 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.HasIndex("EnvironmentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("ParentDeploymentId"); + b.HasIndex("AppId", "Name") .IsUnique(); @@ -136,6 +245,138 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("AppEnvironments"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllowFromNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CustomYaml") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PolicyType") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "Name") + .IsUnique(); + + b.ToTable("AppNetworkPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CpuLimit") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CpuRequest") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("MaxPods") + .HasColumnType("INTEGER"); + + b.Property("MaxPvcs") + .HasColumnType("INTEGER"); + + b.Property("MemoryLimit") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("MemoryRequest") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppQuotas"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("AutoMountToken") + .HasColumnType("INTEGER"); + + b.Property("ServiceAccountName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.ToTable("AppRbacPolicies"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiGroups") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("AppRbacPolicyId") + .HasColumnType("TEXT"); + + b.Property("Resources") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Verbs") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppRbacPolicyId"); + + b.ToTable("AppRbacRules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ApplicationUser", b => { b.Property("Id") @@ -200,6 +441,87 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("AspNetUsers", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OccurredAt") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ResourceName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId"); + + b.HasIndex("OccurredAt"); + + b.ToTable("AuditEvents"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RedisClusterId"); + + b.ToTable("CacheBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Property("Id") @@ -316,12 +638,20 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("Instances") .HasColumnType("INTEGER"); + b.Property("IsExternal") + .HasColumnType("INTEGER"); + b.Property("KubernetesClusterId") .HasColumnType("TEXT"); b.Property("LastError") .HasColumnType("TEXT"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + b.Property("Name") .IsRequired() .HasMaxLength(63) @@ -473,6 +803,9 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("MongoDatabaseId") .HasColumnType("TEXT"); + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + b.Property("SyncEnabled") .HasColumnType("INTEGER"); @@ -484,9 +817,46 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.HasIndex("MongoDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.ToTable("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeploymentId") + .HasColumnType("TEXT"); + + b.Property("HealthStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ReadyReplicas") + .HasColumnType("INTEGER"); + + b.Property("SnapshotAt") + .HasColumnType("TEXT"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TotalReplicas") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeploymentId", "SnapshotAt"); + + b.ToTable("DeploymentHealthSnapshots"); + }); + modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => { b.Property("Id") @@ -512,6 +882,9 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("SortOrder") .HasColumnType("INTEGER"); + b.Property("SourceFile") + .HasColumnType("TEXT"); + b.Property("UpdatedAt") .HasColumnType("TEXT"); @@ -590,6 +963,76 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("DeploymentResources"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(254) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("KubernetesNamespace") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordNonce") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("RegistryType") + .HasColumnType("INTEGER"); + + b.Property("Server") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("VaultId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("VaultId"); + + b.ToTable("DockerRegistryCredentials"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.Property("Id") @@ -644,6 +1087,15 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasMaxLength(253) .HasColumnType("TEXT"); + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("LastHealthCheckAt") + .HasColumnType("TEXT"); + + b.Property("LastStatusCode") + .HasColumnType("INTEGER"); + b.Property("PathPrefix") .IsRequired() .HasMaxLength(200) @@ -674,6 +1126,120 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("ExternalRoutes"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CheckedAt") + .HasColumnType("TEXT"); + + b.Property("IsReachable") + .HasColumnType("INTEGER"); + + b.Property("ResponseMs") + .HasColumnType("INTEGER"); + + b.Property("RouteId") + .HasColumnType("TEXT"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RouteId", "CheckedAt"); + + b.ToTable("ExternalRouteHealthHistories"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TrustedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GitRepositoryId"); + + b.HasIndex("TenantId", "Hostname") + .IsUnique(); + + b.ToTable("GitKnownHosts"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasDefaultValue("main"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Username") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("GitRepositories"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Property("Id") @@ -717,6 +1283,113 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("GroupMemberships"); }); + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClusterComponentId") + .HasColumnType("TEXT"); + + b.Property("CnpgDatabaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegistryUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterComponentId"); + + b.HasIndex("CnpgDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.ToTable("HarborComponentConfigs"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HarborComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LinkedAppId") + .HasColumnType("TEXT"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkedAppId"); + + b.HasIndex("TenantId"); + + b.HasIndex("HarborComponentConfigId", "ProjectName") + .IsUnique(); + + b.ToTable("HarborProjects"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IncidentId"); + + b.ToTable("IncidentNotes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => { b.Property("Id") @@ -751,7 +1424,7 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("Status") .HasColumnType("INTEGER"); - b.Property("StorageLinkId") + b.Property("StorageLinkId") .HasColumnType("TEXT"); b.Property("TenantId") @@ -781,7 +1454,7 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasMaxLength(100) .HasColumnType("TEXT"); - b.Property("ClusterComponentId") + b.Property("ClusterComponentId") .HasColumnType("TEXT"); b.Property("CnpgDatabaseId") @@ -790,6 +1463,13 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + b.Property("TenantId") .HasColumnType("TEXT"); @@ -799,6 +1479,8 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.HasIndex("CnpgDatabaseId"); + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("TenantId"); b.ToTable("KeycloakComponentConfigs"); @@ -827,6 +1509,9 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("KeycloakComponentConfigId") .HasColumnType("TEXT"); + b.Property("KeycloakThemeId") + .HasColumnType("TEXT"); + b.Property("LinkedAppId") .HasColumnType("TEXT"); @@ -843,6 +1528,8 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.HasKey("Id"); + b.HasIndex("KeycloakThemeId"); + b.HasIndex("LinkedAppId"); b.HasIndex("KeycloakComponentConfigId", "RealmName") @@ -851,6 +1538,42 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("KeycloakRealms"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KeycloakComponentConfigId") + .HasColumnType("TEXT"); + + b.Property("LoginTheme") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KeycloakComponentConfigId", "Name") + .IsUnique(); + + b.ToTable("KeycloakThemes"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Property("Id") @@ -892,6 +1615,103 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("StartsAt"); + + b.HasIndex("TenantId", "StartsAt"); + + b.ToTable("MaintenanceWindows"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppDeploymentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExchangeName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("KubernetesSecretName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("LastSyncedAt") + .HasColumnType("TEXT"); + + b.Property("QueueName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SyncEnabled") + .HasColumnType("INTEGER"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Vhost") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppDeploymentId"); + + b.HasIndex("RabbitMQClusterId"); + + b.ToTable("MessagingBindings"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.Property("Id") @@ -939,18 +1759,41 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasMaxLength(100) .HasColumnType("TEXT"); + b.Property("CpuLimit") + .HasColumnType("TEXT"); + + b.Property("CpuRequest") + .HasColumnType("TEXT"); + b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("ExternalUri") + .HasColumnType("TEXT"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + b.Property("KubernetesClusterId") .HasColumnType("TEXT"); b.Property("LastError") .HasColumnType("TEXT"); + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(20); + b.Property("Members") .HasColumnType("INTEGER"); + b.Property("MemoryLimit") + .HasColumnType("TEXT"); + + b.Property("MemoryRequest") + .HasColumnType("TEXT"); + b.Property("MongoVersion") .IsRequired() .HasMaxLength(20) @@ -1027,6 +1870,82 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("MongoDatabases"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SeverityFilter") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("NotificationChannels"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IncidentId") + .HasColumnType("TEXT"); + + b.Property("IsFiring") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Success") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("IncidentId", "ChannelId", "IsFiring"); + + b.ToTable("NotificationDeliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Property("Id") @@ -1072,6 +1991,310 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("OpenStackConnections"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterName") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RabbitMQBackups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BackupSchedule") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("MaxBackups") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(10); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RabbitMQVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Replicas") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StorageLinkId"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RabbitMQClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("PersistenceEnabled") + .HasColumnType("INTEGER"); + + b.Property("RedisVersion") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StorageClass") + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("StorageSize") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("KubernetesClusterId", "Name", "Namespace") + .IsUnique(); + + b.ToTable("RedisClusters"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresInstanceId") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresInstanceId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresDatabases"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + + b.Property("S3Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("StorageLinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + + b.HasIndex("StorageLinkId"); + + b.ToTable("RegisteredPostgresDumps"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminPodName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("AdminUsername") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Namespace") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(253) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("KubernetesClusterId"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("RegisteredPostgresInstances"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Property("Id") @@ -1100,6 +2323,42 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("SecretVaults"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("MeasurementWindowDays") + .HasColumnType("INTEGER"); + + b.Property("TargetPercent") + .HasColumnType("REAL"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId", "CustomerId", "AppId") + .IsUnique(); + + b.ToTable("SlaTargets"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.Property("Id") @@ -1274,6 +2533,9 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("AppId") .HasColumnType("TEXT"); + b.Property("CnpgClusterId") + .HasColumnType("TEXT"); + b.Property("CnpgDatabaseId") .HasColumnType("TEXT"); @@ -1287,6 +2549,12 @@ namespace EntKube.Web.Data.Migrations.Sqlite .IsRequired() .HasColumnType("BLOB"); + b.Property("GitRepositoryId") + .HasColumnType("TEXT"); + + b.Property("KubernetesClusterId") + .HasColumnType("TEXT"); + b.Property("KubernetesNamespace") .HasMaxLength(63) .HasColumnType("TEXT"); @@ -1313,6 +2581,15 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("OpenStackConnectionId") .HasColumnType("TEXT"); + b.Property("RabbitMQClusterId") + .HasColumnType("TEXT"); + + b.Property("RedisClusterId") + .HasColumnType("TEXT"); + + b.Property("RegisteredPostgresDatabaseId") + .HasColumnType("TEXT"); + b.Property("StorageLinkId") .HasColumnType("TEXT"); @@ -1325,20 +2602,37 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Property("VaultId") .HasColumnType("TEXT"); + b.Property("VpnRemoteEndpointId") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("AppId"); + b.HasIndex("CnpgClusterId"); + b.HasIndex("CnpgDatabaseId"); b.HasIndex("ComponentId"); + b.HasIndex("GitRepositoryId"); + + b.HasIndex("KubernetesClusterId"); + b.HasIndex("MongoClusterId"); b.HasIndex("MongoDatabaseId"); + b.HasIndex("RabbitMQClusterId"); + + b.HasIndex("RedisClusterId"); + + b.HasIndex("RegisteredPostgresDatabaseId"); + b.HasIndex("StorageLinkId"); + b.HasIndex("VpnRemoteEndpointId"); + b.HasIndex("VaultId", "AppId", "Name") .IsUnique(); @@ -1348,6 +2642,175 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("VaultSecrets"); }); + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClusterId") + .HasColumnType("TEXT"); + + b.Property("ComponentId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClusterId"); + + b.HasIndex("ComponentId"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnLocalEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("LocalId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PublicIp") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Subnets") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("VpnTunnelId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("VpnTunnelId"); + + b.ToTable("VpnRemoteEndpoints"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ChildLifetime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DpdDelay") + .HasColumnType("INTEGER"); + + b.Property("DpdTimeout") + .HasColumnType("INTEGER"); + + b.Property("EspProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeLifetime") + .HasColumnType("INTEGER"); + + b.Property("IkeProposal") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IkeVersion") + .HasColumnType("INTEGER"); + + b.Property("LastStatusCheckAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TunnelType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("VpnTunnels"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -1476,6 +2939,17 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.HasOne("EntKube.Web.Data.Customer", "Customer") @@ -1507,11 +2981,25 @@ namespace EntKube.Web.Data.Migrations.Sqlite .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany("Deployments") + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.AppDeployment", "ParentDeployment") + .WithMany("ChildDeployments") + .HasForeignKey("ParentDeploymentId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); b.Navigation("Cluster"); b.Navigation("Environment"); + + b.Navigation("GitRepository"); + + b.Navigation("ParentDeployment"); }); modelBuilder.Entity("EntKube.Web.Data.AppEnvironment", b => @@ -1533,6 +3021,79 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Environment"); }); + modelBuilder.Entity("EntKube.Web.Data.AppNetworkPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany("NetworkPolicies") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppQuota", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("Quota") + .HasForeignKey("EntKube.Web.Data.AppQuota", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithOne("RbacPolicy") + .HasForeignKey("EntKube.Web.Data.AppRbacPolicy", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AppRbacRule", b => + { + b.HasOne("EntKube.Web.Data.AppRbacPolicy", "Policy") + .WithMany("Rules") + .HasForeignKey("AppRbacPolicyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Policy"); + }); + + modelBuilder.Entity("EntKube.Web.Data.AuditEvent", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Deployment"); + }); + + modelBuilder.Entity("EntKube.Web.Data.CacheBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany("CacheBindings") + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("RedisCluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") @@ -1640,11 +3201,29 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany("DatabaseBindings") + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("AppDeployment"); b.Navigation("CnpgDatabase"); b.Navigation("MongoDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + }); + + modelBuilder.Entity("EntKube.Web.Data.DeploymentHealthSnapshot", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "Deployment") + .WithMany() + .HasForeignKey("DeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deployment"); }); modelBuilder.Entity("EntKube.Web.Data.DeploymentManifest", b => @@ -1676,6 +3255,31 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("ParentResource"); }); + modelBuilder.Entity("EntKube.Web.Data.DockerRegistryCredential", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.SecretVault", "Vault") + .WithMany() + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Vault"); + }); + modelBuilder.Entity("EntKube.Web.Data.Environment", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1698,6 +3302,43 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Component"); }); + modelBuilder.Entity("EntKube.Web.Data.ExternalRouteHealthHistory", b => + { + b.HasOne("EntKube.Web.Data.ExternalRoute", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitKnownHost", b => + { + b.HasOne("EntKube.Web.Data.GitRepository", null) + .WithMany("KnownHosts") + .HasForeignKey("GitRepositoryId"); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitKnownHosts") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany("GitRepositories") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1728,26 +3369,7 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("User"); }); - modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => - { - b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") - .WithMany("Backups") - .HasForeignKey("KeycloakRealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") - .WithMany() - .HasForeignKey("StorageLinkId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Realm"); - - b.Navigation("StorageLink"); - }); - - modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + modelBuilder.Entity("EntKube.Web.Data.HarborComponentConfig", b => { b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") .WithMany() @@ -1760,6 +3382,11 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasForeignKey("CnpgDatabaseId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") .WithMany() .HasForeignKey("TenantId") @@ -1770,6 +3397,95 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("CnpgDatabase"); + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.HarborProject", b => + { + b.HasOne("EntKube.Web.Data.HarborComponentConfig", "HarborComponentConfig") + .WithMany() + .HasForeignKey("HarborComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.App", "LinkedApp") + .WithMany() + .HasForeignKey("LinkedAppId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HarborComponentConfig"); + + b.Navigation("LinkedApp"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.IncidentNote", b => + { + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Notes") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Incident"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakBackup", b => + { + b.HasOne("EntKube.Web.Data.KeycloakRealm", "Realm") + .WithMany("Backups") + .HasForeignKey("KeycloakRealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Realm"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakComponentConfig", b => + { + b.HasOne("EntKube.Web.Data.ClusterComponent", "ClusterComponent") + .WithMany() + .HasForeignKey("ClusterComponentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") + .WithMany() + .HasForeignKey("CnpgDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClusterComponent"); + + b.Navigation("CnpgDatabase"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("Tenant"); }); @@ -1781,6 +3497,11 @@ namespace EntKube.Web.Data.Migrations.Sqlite .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.KeycloakTheme", "Theme") + .WithMany("Realms") + .HasForeignKey("KeycloakThemeId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.App", "LinkedApp") .WithMany() .HasForeignKey("LinkedAppId") @@ -1789,6 +3510,19 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("ComponentConfig"); b.Navigation("LinkedApp"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.HasOne("EntKube.Web.Data.KeycloakComponentConfig", "ComponentConfig") + .WithMany() + .HasForeignKey("KeycloakComponentConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ComponentConfig"); }); modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => @@ -1810,6 +3544,43 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.MaintenanceWindow", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.MessagingBinding", b => + { + b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") + .WithMany() + .HasForeignKey("AppDeploymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppDeployment"); + + b.Navigation("Cluster"); + }); + modelBuilder.Entity("EntKube.Web.Data.MongoBackup", b => { b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") @@ -1858,6 +3629,36 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("MongoCluster"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.NotificationDelivery", b => + { + b.HasOne("EntKube.Web.Data.NotificationChannel", "Channel") + .WithMany("Deliveries") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.AlertIncident", "Incident") + .WithMany("Deliveries") + .HasForeignKey("IncidentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Incident"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1869,6 +3670,118 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQBackup", b => + { + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "Cluster") + .WithMany("Backups") + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cluster"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("StorageLink"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RedisCluster", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresInstance", "RegisteredPostgresInstance") + .WithMany("Databases") + .HasForeignKey("RegisteredPostgresInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresInstance"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDump", b => + { + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") + .WithMany() + .HasForeignKey("StorageLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegisteredPostgresDatabase"); + + b.Navigation("StorageLink"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KubernetesCluster"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.HasOne("EntKube.Web.Data.Tenant", "Tenant") @@ -1880,6 +3793,31 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Tenant"); }); + modelBuilder.Entity("EntKube.Web.Data.SlaTarget", b => + { + b.HasOne("EntKube.Web.Data.App", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Customer"); + + b.Navigation("Tenant"); + }); + modelBuilder.Entity("EntKube.Web.Data.StorageBinding", b => { b.HasOne("EntKube.Web.Data.AppDeployment", "AppDeployment") @@ -1981,6 +3919,11 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasForeignKey("AppId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgCluster", "CnpgCluster") + .WithMany() + .HasForeignKey("CnpgClusterId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.CnpgDatabase", "CnpgDatabase") .WithMany() .HasForeignKey("CnpgDatabaseId") @@ -1991,6 +3934,16 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasForeignKey("ComponentId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.GitRepository", "GitRepository") + .WithMany() + .HasForeignKey("GitRepositoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.KubernetesCluster", "KubernetesCluster") + .WithMany() + .HasForeignKey("KubernetesClusterId") + .OnDelete(DeleteBehavior.SetNull); + b.HasOne("EntKube.Web.Data.MongoCluster", "MongoCluster") .WithMany() .HasForeignKey("MongoClusterId") @@ -2001,6 +3954,21 @@ namespace EntKube.Web.Data.Migrations.Sqlite .HasForeignKey("MongoDatabaseId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.RabbitMQCluster", "RabbitMQCluster") + .WithMany() + .HasForeignKey("RabbitMQClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RedisCluster", "RedisCluster") + .WithMany() + .HasForeignKey("RedisClusterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EntKube.Web.Data.RegisteredPostgresDatabase", "RegisteredPostgresDatabase") + .WithMany() + .HasForeignKey("RegisteredPostgresDatabaseId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("EntKube.Web.Data.StorageLink", "StorageLink") .WithMany() .HasForeignKey("StorageLinkId"); @@ -2011,19 +3979,86 @@ namespace EntKube.Web.Data.Migrations.Sqlite .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("EntKube.Web.Data.VpnRemoteEndpoint", "VpnRemoteEndpoint") + .WithMany("Secrets") + .HasForeignKey("VpnRemoteEndpointId") + .OnDelete(DeleteBehavior.Cascade); + b.Navigation("App"); + b.Navigation("CnpgCluster"); + b.Navigation("CnpgDatabase"); b.Navigation("Component"); + b.Navigation("GitRepository"); + + b.Navigation("KubernetesCluster"); + b.Navigation("MongoCluster"); b.Navigation("MongoDatabase"); + b.Navigation("RabbitMQCluster"); + + b.Navigation("RedisCluster"); + + b.Navigation("RegisteredPostgresDatabase"); + b.Navigation("StorageLink"); b.Navigation("Vault"); + + b.Navigation("VpnRemoteEndpoint"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnLocalEndpoint", b => + { + b.HasOne("EntKube.Web.Data.KubernetesCluster", "Cluster") + .WithMany() + .HasForeignKey("ClusterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntKube.Web.Data.ClusterComponent", "Component") + .WithMany() + .HasForeignKey("ComponentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("LocalEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cluster"); + + b.Navigation("Component"); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.HasOne("EntKube.Web.Data.VpnTunnel", "Tunnel") + .WithMany("RemoteEndpoints") + .HasForeignKey("VpnTunnelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tunnel"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.HasOne("EntKube.Web.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -2077,17 +4112,34 @@ namespace EntKube.Web.Data.Migrations.Sqlite .IsRequired(); }); + modelBuilder.Entity("EntKube.Web.Data.AlertIncident", b => + { + b.Navigation("Deliveries"); + + b.Navigation("Notes"); + }); + modelBuilder.Entity("EntKube.Web.Data.App", b => { b.Navigation("AppEnvironments"); b.Navigation("Deployments"); + b.Navigation("NetworkPolicies"); + + b.Navigation("Quota"); + + b.Navigation("RbacPolicy"); + b.Navigation("Secrets"); }); modelBuilder.Entity("EntKube.Web.Data.AppDeployment", b => { + b.Navigation("CacheBindings"); + + b.Navigation("ChildDeployments"); + b.Navigation("DatabaseBindings"); b.Navigation("Manifests"); @@ -2097,6 +4149,11 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("StorageBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.AppRbacPolicy", b => + { + b.Navigation("Rules"); + }); + modelBuilder.Entity("EntKube.Web.Data.ClusterComponent", b => { b.Navigation("ExternalRoutes"); @@ -2135,6 +4192,13 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("KubernetesClusters"); }); + modelBuilder.Entity("EntKube.Web.Data.GitRepository", b => + { + b.Navigation("Deployments"); + + b.Navigation("KnownHosts"); + }); + modelBuilder.Entity("EntKube.Web.Data.Group", b => { b.Navigation("Memberships"); @@ -2150,6 +4214,11 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Backups"); }); + modelBuilder.Entity("EntKube.Web.Data.KeycloakTheme", b => + { + b.Navigation("Realms"); + }); + modelBuilder.Entity("EntKube.Web.Data.KubernetesCluster", b => { b.Navigation("Components"); @@ -2167,11 +4236,31 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("DatabaseBindings"); }); + modelBuilder.Entity("EntKube.Web.Data.NotificationChannel", b => + { + b.Navigation("Deliveries"); + }); + modelBuilder.Entity("EntKube.Web.Data.OpenStackConnection", b => { b.Navigation("StorageLinks"); }); + modelBuilder.Entity("EntKube.Web.Data.RabbitMQCluster", b => + { + b.Navigation("Backups"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresDatabase", b => + { + b.Navigation("DatabaseBindings"); + }); + + modelBuilder.Entity("EntKube.Web.Data.RegisteredPostgresInstance", b => + { + b.Navigation("Databases"); + }); + modelBuilder.Entity("EntKube.Web.Data.SecretVault", b => { b.Navigation("Secrets"); @@ -2188,6 +4277,10 @@ namespace EntKube.Web.Data.Migrations.Sqlite b.Navigation("Environments"); + b.Navigation("GitKnownHosts"); + + b.Navigation("GitRepositories"); + b.Navigation("Groups"); b.Navigation("KubernetesClusters"); @@ -2203,6 +4296,18 @@ namespace EntKube.Web.Data.Migrations.Sqlite { b.Navigation("Memberships"); }); + + modelBuilder.Entity("EntKube.Web.Data.VpnRemoteEndpoint", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("EntKube.Web.Data.VpnTunnel", b => + { + b.Navigation("LocalEndpoints"); + + b.Navigation("RemoteEndpoints"); + }); #pragma warning restore 612, 618 } } diff --git a/src/EntKube.Web/Data/MongoCluster.cs b/src/EntKube.Web/Data/MongoCluster.cs index 1786a04..f945b1a 100644 --- a/src/EntKube.Web/Data/MongoCluster.cs +++ b/src/EntKube.Web/Data/MongoCluster.cs @@ -64,6 +64,26 @@ public class MongoCluster /// public required string StorageSize { get; set; } + /// + /// CPU request for each MongoDB pod (e.g. "500m", "2"). Null means no request is set. + /// + public string? CpuRequest { get; set; } + + /// + /// CPU limit for each MongoDB pod (e.g. "2", "4"). Null means no limit is set. + /// + public string? CpuLimit { get; set; } + + /// + /// Memory request for each MongoDB pod (e.g. "2Gi", "8Gi"). Null means no request is set. + /// + public string? MemoryRequest { get; set; } + + /// + /// Memory limit for each MongoDB pod (e.g. "8Gi", "16Gi"). Null means no limit is set. + /// + public string? MemoryLimit { get; set; } + /// /// Optional link to an S3 bucket for automated backups and point-in-time restore. /// When set, the cluster is configured with Percona Backup for MongoDB (PBM). @@ -82,6 +102,27 @@ public class MongoCluster /// public int RetentionDays { get; set; } = 30; + /// + /// Maximum number of completed backup Jobs to keep in Kubernetes. When exceeded + /// during a cleanup, the oldest completed Jobs are deleted from K8s and the DB. + /// Does not affect backup data in S3 — use S3 lifecycle rules or RetentionDays for that. + /// + public int MaxBackups { get; set; } = 20; + + /// + /// True when the cluster was registered from an existing deployment (not provisioned by EntKube). + /// Delete should offer "Unregister" (remove from EntKube only) in addition to full deletion. + /// + public bool IsExternal { get; set; } + + /// + /// MongoDB connection URI for external clusters not managed by the Community Operator + /// (e.g. mongodb://user:pass@host:27017/?authSource=admin). When set, enables S3 backup, + /// database discovery, and migration to a managed cluster without pod exec. + /// Treat as a sensitive credential — do not log or expose in responses. + /// + public string? ExternalUri { get; set; } + /// /// Current lifecycle status of the cluster. /// diff --git a/src/EntKube.Web/Data/NotificationChannel.cs b/src/EntKube.Web/Data/NotificationChannel.cs new file mode 100644 index 0000000..0671ffb --- /dev/null +++ b/src/EntKube.Web/Data/NotificationChannel.cs @@ -0,0 +1,19 @@ +namespace EntKube.Web.Data; + +public enum NotificationChannelType { Slack, Teams, Email, Webhook } +public enum AlertSeverityFilter { All, WarningAndAbove, CriticalOnly } + +public class NotificationChannel +{ + public Guid Id { get; set; } + public Guid TenantId { get; set; } + public required string Name { get; set; } + public NotificationChannelType Type { get; set; } + public required string ConfigurationJson { get; set; } + public bool IsEnabled { get; set; } = true; + public AlertSeverityFilter SeverityFilter { get; set; } = AlertSeverityFilter.All; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public Tenant Tenant { get; set; } = null!; + public List Deliveries { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/NotificationDelivery.cs b/src/EntKube.Web/Data/NotificationDelivery.cs new file mode 100644 index 0000000..22f12f4 --- /dev/null +++ b/src/EntKube.Web/Data/NotificationDelivery.cs @@ -0,0 +1,15 @@ +namespace EntKube.Web.Data; + +public class NotificationDelivery +{ + public Guid Id { get; set; } + public Guid IncidentId { get; set; } + public Guid ChannelId { get; set; } + public bool IsFiring { get; set; } + public DateTime SentAt { get; set; } = DateTime.UtcNow; + public bool Success { get; set; } + public string? Error { get; set; } + + public AlertIncident Incident { get; set; } = null!; + public NotificationChannel Channel { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/RabbitMQBackup.cs b/src/EntKube.Web/Data/RabbitMQBackup.cs new file mode 100644 index 0000000..a68848d --- /dev/null +++ b/src/EntKube.Web/Data/RabbitMQBackup.cs @@ -0,0 +1,44 @@ +namespace EntKube.Web.Data; + +public enum RabbitMQBackupStatus +{ + Creating, + Ready, + Failed +} + +/// +/// A RabbitMQ definitions backup — the full broker topology (vhosts, exchanges, queues, +/// bindings, policies, users, permissions) exported as definitions.json via rabbitmqctl +/// and stored in an S3 bucket. Used for disaster recovery and cross-environment migration. +/// +public class RabbitMQBackup +{ + public Guid Id { get; set; } + + public Guid RabbitMQClusterId { get; set; } + + public Guid TenantId { get; set; } + + public Guid? StorageLinkId { get; set; } + + /// S3 object key (e.g. "rabbitmq/my-cluster/20260601-120000.json"). + public required string ObjectKey { get; set; } + + /// Snapshot of the cluster name at backup time, for display after deletion. + public required string ClusterName { get; set; } + + public long SizeBytes { get; set; } + + public RabbitMQBackupStatus Status { get; set; } = RabbitMQBackupStatus.Creating; + + public string? LastError { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? CompletedAt { get; set; } + + // Navigation + public RabbitMQCluster Cluster { get; set; } = null!; + public StorageLink? StorageLink { get; set; } +} diff --git a/src/EntKube.Web/Data/RabbitMQCluster.cs b/src/EntKube.Web/Data/RabbitMQCluster.cs new file mode 100644 index 0000000..7ffc6bc --- /dev/null +++ b/src/EntKube.Web/Data/RabbitMQCluster.cs @@ -0,0 +1,72 @@ +namespace EntKube.Web.Data; + +public enum RabbitMQClusterStatus +{ + Creating, + Running, + Failed, + Deleting +} + +/// +/// A managed RabbitMQ cluster provisioned via the RabbitMQ Cluster Operator. +/// EntKube owns the full lifecycle: create, delete, backup (definitions export), restore. +/// Topology (vhosts, queues, exchanges) is managed declaratively by the RabbitMQ +/// Messaging Topology Operator and discovered live from Kubernetes. +/// +public class RabbitMQCluster +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + public Guid KubernetesClusterId { get; set; } + + /// + /// The name of the RabbitmqCluster resource (metadata.name). Lowercase, DNS-safe. + /// The operator derives all child resource names from this (secret, service, StatefulSet). + /// + public required string Name { get; set; } + + public required string Namespace { get; set; } + + /// + /// RabbitMQ version tag (e.g. "3.13", "4.0"). Maps to the rabbitmq:{version}-management image. + /// + public required string RabbitMQVersion { get; set; } + + /// + /// Number of RabbitMQ nodes. 1 = standalone, 3 = quorum-capable HA cluster. + /// + public int Replicas { get; set; } = 3; + + /// PVC storage size per node (e.g. "10Gi"). + public required string StorageSize { get; set; } + + /// Optional StorageClass name. When null the cluster default is used. + public string? StorageClass { get; set; } + + /// + /// Optional link to an S3 bucket used for definitions.json backups. + /// When set, the MessagingTab shows backup/restore controls. + /// + public Guid? StorageLinkId { get; set; } + + /// Cron schedule for automated backups. Null = on-demand only. + public string? BackupSchedule { get; set; } + + /// Maximum number of completed backup records to retain in the DB. + public int MaxBackups { get; set; } = 10; + + public RabbitMQClusterStatus Status { get; set; } = RabbitMQClusterStatus.Creating; + + public string? LastError { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public KubernetesCluster KubernetesCluster { get; set; } = null!; + public StorageLink? StorageLink { get; set; } + public ICollection Backups { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/RedisCluster.cs b/src/EntKube.Web/Data/RedisCluster.cs new file mode 100644 index 0000000..15d9bd6 --- /dev/null +++ b/src/EntKube.Web/Data/RedisCluster.cs @@ -0,0 +1,60 @@ +namespace EntKube.Web.Data; + +public enum RedisClusterStatus +{ + Creating, + Running, + Failed, + Deleting +} + +/// +/// A managed Redis cluster provisioned via the OT-Container-Kit Redis Operator. +/// Applies a RedisCluster CRD (redis.redis.opstreelabs.in/v1beta2) which the +/// operator reconciles into a sharded Redis cluster StatefulSet. +/// +/// EntKube generates the auth password, stores it in the tenant vault, and +/// creates a Kubernetes Secret before applying the CRD so the operator can +/// read the credentials at startup. +/// +public class RedisCluster +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + public Guid KubernetesClusterId { get; set; } + + /// + /// The RedisCluster resource name (metadata.name). Lowercase, DNS-safe. + /// The operator derives all child resources from this name. + /// + public required string Name { get; set; } + + public required string Namespace { get; set; } + + /// Number of leader shards. Each shard gets one follower. Min 3 for cluster mode. + public int ClusterSize { get; set; } = 3; + + /// Redis version tag used on the operator image (e.g. "v7", "v7.0.15"). + public required string RedisVersion { get; set; } + + /// PVC storage size per node (e.g. "1Gi", "10Gi"). + public required string StorageSize { get; set; } + + /// Optional StorageClass override. Null uses the cluster default. + public string? StorageClass { get; set; } + + /// When true, PVCs are created for each pod for data persistence. + public bool PersistenceEnabled { get; set; } = true; + + public RedisClusterStatus Status { get; set; } = RedisClusterStatus.Creating; + + public string? LastError { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public KubernetesCluster KubernetesCluster { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/RedisClusterDetail.cs b/src/EntKube.Web/Data/RedisClusterDetail.cs new file mode 100644 index 0000000..24188c2 --- /dev/null +++ b/src/EntKube.Web/Data/RedisClusterDetail.cs @@ -0,0 +1,21 @@ +namespace EntKube.Web.Data; + +public class RedisClusterDetail +{ + public required RedisCluster Cluster { get; set; } + public List Pods { get; set; } = []; + public int ReadyLeaders { get; set; } + public int ReadyFollowers { get; set; } + public string Phase { get; set; } = "Unknown"; +} + +public class RedisPodInfo +{ + public required string Name { get; set; } + public required string Role { get; set; } // "leader" or "follower" + public required string Status { get; set; } + public bool Ready { get; set; } + public string? Node { get; set; } + public DateTime? StartTime { get; set; } + public int Restarts { get; set; } +} diff --git a/src/EntKube.Web/Data/RegisteredPostgresDatabase.cs b/src/EntKube.Web/Data/RegisteredPostgresDatabase.cs new file mode 100644 index 0000000..fa19532 --- /dev/null +++ b/src/EntKube.Web/Data/RegisteredPostgresDatabase.cs @@ -0,0 +1,42 @@ +namespace EntKube.Web.Data; + +/// +/// Status of a database managed within a RegisteredPostgresInstance. +/// +public enum RegisteredPostgresDatabaseStatus +{ + Creating, + Ready, + Deleting, + Failed +} + +/// +/// A logical PostgreSQL database within a RegisteredPostgresInstance. +/// EntKube created (or imported) this database and manages its owner role +/// and vault credentials — but does not own the Postgres server itself. +/// +public class RegisteredPostgresDatabase +{ + public Guid Id { get; set; } + + public Guid RegisteredPostgresInstanceId { get; set; } + + /// + /// The PostgreSQL database name (e.g. "keycloak"). + /// + public required string Name { get; set; } + + /// + /// The PostgreSQL role that owns this database (e.g. "keycloak_owner"). + /// + public required string Owner { get; set; } + + public RegisteredPostgresDatabaseStatus Status { get; set; } = RegisteredPostgresDatabaseStatus.Creating; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public RegisteredPostgresInstance RegisteredPostgresInstance { get; set; } = null!; + public ICollection DatabaseBindings { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/RegisteredPostgresDump.cs b/src/EntKube.Web/Data/RegisteredPostgresDump.cs new file mode 100644 index 0000000..f3646f5 --- /dev/null +++ b/src/EntKube.Web/Data/RegisteredPostgresDump.cs @@ -0,0 +1,24 @@ +namespace EntKube.Web.Data; + +/// +/// A pg_dump backup of a RegisteredPostgresDatabase stored as plain SQL in an S3 bucket. +/// Created manually from the Databases tab. Can be restored to a new CNPG database. +/// +public class RegisteredPostgresDump +{ + public Guid Id { get; set; } + public Guid RegisteredPostgresDatabaseId { get; set; } + public Guid StorageLinkId { get; set; } + + /// + /// S3 object key, e.g. pg-dumps/my-instance/keycloak/2026-05-22T09-15-00Z.sql + /// + public required string S3Key { get; set; } + + public long SizeBytes { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public RegisteredPostgresDatabase RegisteredPostgresDatabase { get; set; } = null!; + public StorageLink StorageLink { get; set; } = null!; +} diff --git a/src/EntKube.Web/Data/RegisteredPostgresInstance.cs b/src/EntKube.Web/Data/RegisteredPostgresInstance.cs new file mode 100644 index 0000000..f40b04c --- /dev/null +++ b/src/EntKube.Web/Data/RegisteredPostgresInstance.cs @@ -0,0 +1,66 @@ +namespace EntKube.Web.Data; + +/// +/// A vanilla PostgreSQL instance running inside a Kubernetes cluster that is +/// not managed by the CloudNativePG operator. Registered so EntKube can manage +/// its databases and credentials without owning the server lifecycle. +/// +/// SQL is executed via kubectl exec into the postgres pod, using the admin +/// credentials stored in the vault. +/// +public class RegisteredPostgresInstance +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// + /// The Kubernetes cluster where this Postgres instance runs. + /// + public Guid KubernetesClusterId { get; set; } + + /// + /// Human-readable display name (e.g. "Keycloak DB – Old cluster"). + /// + public required string Name { get; set; } + + /// + /// The Kubernetes namespace where the Postgres pod and service live. + /// + public required string Namespace { get; set; } + + /// + /// The Kubernetes Service name used as the HOST in connection credentials + /// (e.g. "postgres" → resolves as postgres.namespace.svc.cluster.local). + /// + public required string ServiceName { get; set; } + + /// + /// TCP port the Postgres service listens on. Defaults to 5432. + /// + public int Port { get; set; } = 5432; + + /// + /// The name of the pod to kubectl exec into for admin SQL. + /// Must be the primary/standalone pod (not a replica). + /// + public required string AdminPodName { get; set; } + + /// + /// The PostgreSQL superuser username for running CREATE/DROP DATABASE commands. + /// Typically "postgres". The password is stored in the vault. + /// + public string AdminUsername { get; set; } = "postgres"; + + /// + /// Optional free-text notes (e.g. "Keycloak DB provisioned by Helm in 2023"). + /// + public string? Notes { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public KubernetesCluster KubernetesCluster { get; set; } = null!; + public ICollection Databases { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/SlaTarget.cs b/src/EntKube.Web/Data/SlaTarget.cs new file mode 100644 index 0000000..0223bfa --- /dev/null +++ b/src/EntKube.Web/Data/SlaTarget.cs @@ -0,0 +1,16 @@ +namespace EntKube.Web.Data; + +public class SlaTarget +{ + public Guid Id { get; set; } + public Guid TenantId { get; set; } + public Guid? CustomerId { get; set; } + public Guid? AppId { get; set; } + public double TargetPercent { get; set; } = 99.9; + public int MeasurementWindowDays { get; set; } = 30; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public Tenant Tenant { get; set; } = null!; + public Customer? Customer { get; set; } + public App? App { get; set; } +} diff --git a/src/EntKube.Web/Data/Tenant.cs b/src/EntKube.Web/Data/Tenant.cs index acde34e..8c6c24b 100644 --- a/src/EntKube.Web/Data/Tenant.cs +++ b/src/EntKube.Web/Data/Tenant.cs @@ -29,5 +29,7 @@ public class Tenant public ICollection Environments { get; set; } = []; public ICollection Customers { get; set; } = []; public ICollection KubernetesClusters { get; set; } = []; + public ICollection GitRepositories { get; set; } = []; + public ICollection GitKnownHosts { get; set; } = []; public SecretVault? Vault { get; set; } } diff --git a/src/EntKube.Web/Data/VaultSecret.cs b/src/EntKube.Web/Data/VaultSecret.cs index df14c5f..0058e03 100644 --- a/src/EntKube.Web/Data/VaultSecret.cs +++ b/src/EntKube.Web/Data/VaultSecret.cs @@ -51,6 +51,11 @@ public class VaultSecret /// public Guid? OpenStackConnectionId { get; set; } + /// + /// If set, this secret belongs to a CNPG cluster (e.g. superuser credentials). + /// + public Guid? CnpgClusterId { get; set; } + /// /// If set, this secret belongs to a CNPG database (connection credentials). /// @@ -66,11 +71,44 @@ public class VaultSecret /// public Guid? MongoClusterId { get; set; } + /// + /// If set, this secret belongs to a registered (non-CNPG) PostgreSQL database. + /// + public Guid? RegisteredPostgresDatabaseId { get; set; } + + /// + /// If set, this secret belongs to a managed RabbitMQ cluster (e.g. admin credentials). + /// + public Guid? RabbitMQClusterId { get; set; } + + /// + /// If set, this secret belongs to a managed Redis cluster (connection credentials and auth password). + /// + public Guid? RedisClusterId { get; set; } + + /// + /// If set, this secret belongs to a VPN remote endpoint (e.g. PSK or certificate). + /// + public Guid? VpnRemoteEndpointId { get; set; } + + /// + /// If set, this secret belongs to a Git repository credential (PAT, SSH key, or password). + /// Secret names follow the convention: "PAT", "SSH_PRIVATE_KEY", "PASSWORD". + /// + public Guid? GitRepositoryId { get; set; } + /// /// When true, this secret will be synced to Kubernetes as a Secret resource. /// public bool SyncToKubernetes { get; set; } + /// + /// The target Kubernetes cluster for the synced secret. + /// Required for app-scoped secrets where the cluster cannot be inferred + /// from a component or storage link relationship. + /// + public Guid? KubernetesClusterId { get; set; } + /// /// The target Kubernetes Secret name (e.g. "my-app-secrets"). /// Multiple vault secrets can target the same K8s Secret as different data keys. @@ -91,7 +129,14 @@ public class VaultSecret public App? App { get; set; } public ClusterComponent? Component { get; set; } public StorageLink? StorageLink { get; set; } + public CnpgCluster? CnpgCluster { get; set; } public CnpgDatabase? CnpgDatabase { get; set; } public MongoDatabase? MongoDatabase { get; set; } public MongoCluster? MongoCluster { get; set; } + public RegisteredPostgresDatabase? RegisteredPostgresDatabase { get; set; } + public KubernetesCluster? KubernetesCluster { get; set; } + public RabbitMQCluster? RabbitMQCluster { get; set; } + public RedisCluster? RedisCluster { get; set; } + public VpnRemoteEndpoint? VpnRemoteEndpoint { get; set; } + public GitRepository? GitRepository { get; set; } } diff --git a/src/EntKube.Web/Data/VpnLocalEndpoint.cs b/src/EntKube.Web/Data/VpnLocalEndpoint.cs new file mode 100644 index 0000000..375b3b8 --- /dev/null +++ b/src/EntKube.Web/Data/VpnLocalEndpoint.cs @@ -0,0 +1,49 @@ +namespace EntKube.Web.Data; + +/// +/// A cluster on the platform's side that participates in a VPN tunnel. +/// For SiteToSite tunnels this is the cluster running the StrongSwan gateway pod. +/// For ClusterMesh tunnels each participating cluster has one of these entries; +/// the cluster with Role=Gateway also hosts the Submariner broker. +/// +public class VpnLocalEndpoint +{ + public Guid Id { get; set; } + + public Guid VpnTunnelId { get; set; } + + public Guid ClusterId { get; set; } + + /// + /// The ClusterComponent that represents the gateway pod (StrongSwan or Submariner) on + /// this cluster. Null until the component has been registered. + /// + public Guid? ComponentId { get; set; } + + /// + /// JSON array of CIDRs that are reachable through this endpoint, + /// e.g. ["10.244.0.0/16","10.96.0.0/12"]. Entered manually by the operator. + /// + public required string Subnets { get; set; } + + /// + /// External IP of the gateway. Auto-populated from the LoadBalancer Service + /// status after install, or set manually. + /// + public string? PublicIp { get; set; } + + /// + /// Gateway = primary cluster (hosts StrongSwan for SiteToSite, or Submariner broker for ClusterMesh). + /// Participant = additional cluster in a ClusterMesh that gets a Submariner gateway only. + /// + public VpnEndpointRole Role { get; set; } = VpnEndpointRole.Gateway; + + public VpnEndpointStatus Status { get; set; } = VpnEndpointStatus.Pending; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public VpnTunnel Tunnel { get; set; } = null!; + public KubernetesCluster Cluster { get; set; } = null!; + public ClusterComponent? Component { get; set; } +} diff --git a/src/EntKube.Web/Data/VpnRemoteEndpoint.cs b/src/EntKube.Web/Data/VpnRemoteEndpoint.cs new file mode 100644 index 0000000..0d9aefc --- /dev/null +++ b/src/EntKube.Web/Data/VpnRemoteEndpoint.cs @@ -0,0 +1,54 @@ +namespace EntKube.Web.Data; + +/// +/// An external (non-platform) site that a SiteToSite VPN tunnel connects to, +/// e.g. a customer data centre or a cloud VGW. +/// The PSK (or certificate) is stored as a VaultSecret scoped to this endpoint. +/// +public class VpnRemoteEndpoint +{ + public Guid Id { get; set; } + + public Guid VpnTunnelId { get; set; } + + /// Human-friendly label, e.g. "Customer HQ Frankfurt". + public required string Name { get; set; } + + /// Public IP of the remote gateway. + public required string PublicIp { get; set; } + + /// + /// JSON array of CIDRs behind the remote gateway, + /// e.g. ["192.168.10.0/24","172.16.0.0/16"]. + /// + public required string Subnets { get; set; } + + public VpnAuthMode AuthMode { get; set; } = VpnAuthMode.Psk; + + /// + /// Our IKE identity sent to this peer. Null means use the gateway's public IP. + /// Examples: @vpn.example.com (FQDN), 203.0.113.1 (IP), C=SE,O=Org,CN=vpn (DN). + /// + public string? LocalId { get; set; } + + /// + /// Expected IKE identity from the remote peer. Null means accept any. + /// Should be set to prevent impersonation. + /// + public string? RemoteId { get; set; } + + public VpnConnectionStatus Status { get; set; } = VpnConnectionStatus.Unknown; + + public DateTime? LastSeenAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public VpnTunnel Tunnel { get; set; } = null!; + + /// + /// Vault secrets scoped to this endpoint. For PSK auth this contains one + /// secret named "PSK". For certificate auth: "CERT" and optionally "KEY". + /// + public ICollection Secrets { get; set; } = []; +} diff --git a/src/EntKube.Web/Data/VpnTunnel.cs b/src/EntKube.Web/Data/VpnTunnel.cs new file mode 100644 index 0000000..064dd54 --- /dev/null +++ b/src/EntKube.Web/Data/VpnTunnel.cs @@ -0,0 +1,97 @@ +namespace EntKube.Web.Data; + +public enum VpnTunnelType +{ + SiteToSite, + ClusterMesh +} + +public enum VpnTunnelStatus +{ + Draft, + Deploying, + Active, + Degraded, + Failed, + Disabled +} + +public enum VpnEndpointRole +{ + /// Primary gateway — hosts StrongSwan (SiteToSite) or Submariner broker (ClusterMesh). + Gateway, + /// Participant cluster in a ClusterMesh that gets a Submariner gateway (not the broker). + Participant +} + +public enum VpnEndpointStatus +{ + Pending, + Ready, + Error +} + +public enum VpnAuthMode +{ + Psk, + Certificate +} + +public enum VpnConnectionStatus +{ + Unknown, + Connected, + Disconnected +} + +/// +/// A managed VPN tunnel scoped to a tenant. Two types are supported: +/// SiteToSite (StrongSwan IPsec to an external network) and ClusterMesh +/// (Submariner IPsec between K8s clusters within the tenant). +/// +public class VpnTunnel +{ + public Guid Id { get; set; } + + public Guid TenantId { get; set; } + + /// Human-friendly name, e.g. "Customer HQ Link". Unique within a tenant. + public required string Name { get; set; } + + public string? Description { get; set; } + + public VpnTunnelType TunnelType { get; set; } + + public VpnTunnelStatus Status { get; set; } = VpnTunnelStatus.Draft; + + // ── IKEv2 crypto settings ── + + public int IkeVersion { get; set; } = 2; + + /// IKE phase-1 proposal, e.g. "aes256-sha256-modp2048". + public string IkeProposal { get; set; } = "aes256-sha256-modp2048"; + + /// ESP phase-2 proposal, e.g. "aes256gcm16-sha256". + public string EspProposal { get; set; } = "aes256gcm16-sha256"; + + /// Seconds between DPD liveness checks. + public int DpdDelay { get; set; } = 30; + + /// Seconds before a peer is declared dead after no DPD response. + public int DpdTimeout { get; set; } = 150; + + /// Seconds before the IKE SA (phase 1) is renegotiated. Default 24h. + public int IkeLifetime { get; set; } = 86400; + + /// Seconds before the Child SA (phase 2 / ESP) is renegotiated. Default 1h. + public int ChildLifetime { get; set; } = 3600; + + public DateTime? LastStatusCheckAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation + public Tenant Tenant { get; set; } = null!; + public ICollection LocalEndpoints { get; set; } = []; + public ICollection RemoteEndpoints { get; set; } = []; +} diff --git a/src/EntKube.Web/EntKube.Web.csproj b/src/EntKube.Web/EntKube.Web.csproj index 076255a..ccdb637 100644 --- a/src/EntKube.Web/EntKube.Web.csproj +++ b/src/EntKube.Web/EntKube.Web.csproj @@ -14,7 +14,9 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/EntKube.Web/Program.cs b/src/EntKube.Web/Program.cs index 1e0a03e..41b9a2e 100644 --- a/src/EntKube.Web/Program.cs +++ b/src/EntKube.Web/Program.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; +using EntKube.Web.Authorization; using EntKube.Web.Client.Pages; using EntKube.Web.Components; using EntKube.Web.Components.Account; @@ -11,7 +13,7 @@ namespace EntKube.Web; public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -85,13 +87,22 @@ public class Program options.SignIn.RequireConfirmedAccount = true; options.Stores.SchemaVersion = IdentitySchemaVersions.Version3; }) + .AddRoles() .AddEntityFrameworkStores() .AddSignInManager() .AddDefaultTokenProviders(); + builder.Services.AddAuthorization(options => + { + options.AddPolicy("HasTenantAccess", policy => + policy.Requirements.Add(new HasTenantAccessRequirement())); + }); + builder.Services.AddScoped(); + builder.Services.AddSingleton, IdentityNoOpEmailSender>(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); + builder.Services.AddScoped(); // Vault encryption: the root key is loaded from configuration. // In production this should come from a secure source (env var, key vault, etc.) @@ -100,6 +111,7 @@ public class Program byte[] rootKey = Convert.FromBase64String(rootKeyBase64); builder.Services.AddSingleton(new VaultEncryptionService(rootKey)); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -109,11 +121,51 @@ public class Program builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(sp => sp.GetRequiredService()); + + builder.Services.AddHttpClient("Notifications", client => + { + client.Timeout = TimeSpan.FromSeconds(10); + }); + + // Dedicated client for external route health probes — short timeout, + // follows redirects, ignores SSL cert errors for internal/self-signed endpoints. + builder.Services.AddHttpClient("RouteHealth", client => + { + client.Timeout = TimeSpan.FromSeconds(15); + }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + AllowAutoRedirect = true, + MaxAutomaticRedirections = 3, + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }); WebApplication app = builder.Build(); @@ -125,6 +177,24 @@ public class Program { ApplicationDbContext db = scope.ServiceProvider.GetRequiredService(); MigrateWithRetry(db, app.Logger); + + RoleManager roleManager = scope.ServiceProvider.GetRequiredService>(); + if (!await roleManager.RoleExistsAsync("Admin")) + await roleManager.CreateAsync(new IdentityRole("Admin")); + + // If Seed:AdminEmail is set in config, ensure that user gets the Admin role. + // Useful for bootstrapping a fresh environment or recovering access. + string? seedAdminEmail = builder.Configuration.GetValue("Seed:AdminEmail"); + if (!string.IsNullOrWhiteSpace(seedAdminEmail)) + { + UserManager userManager = scope.ServiceProvider.GetRequiredService>(); + ApplicationUser? adminUser = await userManager.FindByEmailAsync(seedAdminEmail); + if (adminUser is not null && !await userManager.IsInRoleAsync(adminUser, "Admin")) + { + await userManager.AddToRoleAsync(adminUser, "Admin"); + app.Logger.LogInformation("Granted Admin role to seeded user {Email}.", seedAdminEmail); + } + } } // Configure the HTTP request pipeline. @@ -154,6 +224,38 @@ public class Program // Add additional endpoints required by the Identity /Account Razor components. app.MapAdditionalIdentityEndpoints(); + // Backup download — streams a gzip-compressed JSON bundle of the full app state. + // Requires the Admin role; the bundle contains plaintext secrets so access is restricted. + app.MapGet("/api/admin/backup", async (BackupService backupService, HttpContext httpContext) => + { + System.Security.Claims.ClaimsPrincipal user = httpContext.User; + if (user.Identity?.IsAuthenticated != true || !user.IsInRole("Admin")) + return Results.Forbid(); + + string performedBy = user.Identity?.Name ?? user.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value ?? "unknown"; + byte[] data = await backupService.ExportAsync(performedBy); + string filename = $"entkube-backup-{DateTime.UtcNow:yyyy-MM-dd-HHmmss}.json.gz"; + return Results.File(data, "application/gzip", filename); + }).RequireAuthorization(); + + // Git webhook endpoint — receives push events from GitHub, Azure DevOps, etc. + // Route: POST /api/git/webhook/{tenantSlug} + // No authentication required (public endpoint); signature verification is + // optional and done per-repo via the vault-stored WEBHOOK_SECRET. + app.MapPost("/api/git/webhook/{tenantSlug}", async ( + string tenantSlug, + HttpContext httpContext, + GitWebhookService webhookService, + CancellationToken ct) => + { + using StreamReader reader = new(httpContext.Request.Body); + string body = await reader.ReadToEndAsync(ct); + string? signature = httpContext.Request.Headers["X-Hub-Signature-256"].FirstOrDefault(); + + string result = await webhookService.HandleAsync(tenantSlug, body, signature, ct); + return Results.Ok(result); + }); + app.Run(); } diff --git a/src/EntKube.Web/Services/AlertSyncService.cs b/src/EntKube.Web/Services/AlertSyncService.cs new file mode 100644 index 0000000..03d2696 --- /dev/null +++ b/src/EntKube.Web/Services/AlertSyncService.cs @@ -0,0 +1,214 @@ +using Microsoft.EntityFrameworkCore; +using EntKube.Web.Data; + +namespace EntKube.Web.Services; + +public class AlertSyncService( + IServiceScopeFactory scopeFactory, + ILogger logger) : BackgroundService +{ + private static readonly TimeSpan Interval = TimeSpan.FromMinutes(2); + private static readonly TimeSpan StartupDelay = TimeSpan.FromSeconds(30); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(StartupDelay, stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + await SyncAllClustersAsync(stoppingToken); + await Task.Delay(Interval, stoppingToken); + } + } + + private async Task SyncAllClustersAsync(CancellationToken ct) + { + List clusterIds; + using (IServiceScope scope = scopeFactory.CreateScope()) + { + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Only sync clusters that have a kube-prometheus-stack component installed + clusterIds = await db.ClusterComponents + .Where(c => c.HelmChartName == "kube-prometheus-stack" + && c.Status == ComponentStatus.Installed) + .Select(c => c.ClusterId) + .Distinct() + .ToListAsync(ct); + } + + foreach (Guid clusterId in clusterIds) + { + try + { + await SyncClusterAsync(clusterId, ct); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Alert sync failed for cluster {ClusterId}", clusterId); + } + } + } + + private async Task SyncClusterAsync(Guid clusterId, CancellationToken ct) + { + using IServiceScope scope = scopeFactory.CreateScope(); + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + var prometheusService = scope.ServiceProvider.GetRequiredService(); + var notificationService = scope.ServiceProvider.GetRequiredService(); + + // Fetch current alerts from Alertmanager + KubernetesOperationResult> alertsResult = + await prometheusService.GetAlertsAsync(clusterId, ct); + + if (!alertsResult.IsSuccess) + { + logger.LogDebug("Could not fetch alerts for cluster {ClusterId}: {Error}", + clusterId, alertsResult.Error); + return; + } + + // Deduplicate by fingerprint in case Alertmanager returns the same alert twice + Dictionary liveAlertsByFingerprint = alertsResult.Data! + .Where(a => !string.IsNullOrEmpty(a.Fingerprint)) + .GroupBy(a => a.Fingerprint) + .ToDictionary(g => g.Key, g => g.First()); + + List liveAlerts = [.. liveAlertsByFingerprint.Values]; + HashSet liveFingerprints = liveAlertsByFingerprint.Keys.ToHashSet(); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Load ALL incidents for this cluster — including Resolved — so that re-fires + // can reactivate an existing row rather than insert a duplicate and violate + // the unique (ClusterId, Fingerprint) constraint. + List allIncidents = await db.AlertIncidents + .Where(i => i.ClusterId == clusterId) + .ToListAsync(ct); + + Dictionary byFingerprint = allIncidents + .ToDictionary(i => i.Fingerprint); + + List openIncidents = allIncidents + .Where(i => i.Status == IncidentStatus.Active || i.Status == IncidentStatus.Acknowledged) + .ToList(); + + HashSet openFingerprints = openIncidents + .Select(i => i.Fingerprint) + .ToHashSet(); + + DateTime now = DateTime.UtcNow; + List newIncidents = []; + + // Create or reactivate incidents for alerts not already tracked as open + foreach (AlertInfo alert in liveAlerts) + { + if (string.IsNullOrEmpty(alert.Fingerprint)) continue; + if (openFingerprints.Contains(alert.Fingerprint)) continue; + + if (byFingerprint.TryGetValue(alert.Fingerprint, out AlertIncident? existing)) + { + // Re-fire of a previously resolved alert — reactivate in place + existing.Status = IncidentStatus.Active; + existing.StartsAt = alert.StartsAt == default ? now : alert.StartsAt; + existing.EndsAt = null; + existing.ResolvedAt = null; + existing.AcknowledgedBy = null; + existing.AcknowledgedAt = null; + existing.AlertName = alert.Name; + existing.Severity = alert.Severity; + existing.Summary = alert.Summary; + existing.Description = alert.Description; + existing.LabelsJson = System.Text.Json.JsonSerializer.Serialize(alert.Labels); + existing.UpdatedAt = now; + newIncidents.Add(existing); + } + else + { + AlertIncident incident = new() + { + ClusterId = clusterId, + Fingerprint = alert.Fingerprint, + AlertName = alert.Name, + Severity = alert.Severity, + Summary = alert.Summary, + Description = alert.Description, + LabelsJson = System.Text.Json.JsonSerializer.Serialize(alert.Labels), + StartsAt = alert.StartsAt == default ? now : alert.StartsAt, + Status = IncidentStatus.Active, + CreatedAt = now, + UpdatedAt = now + }; + db.AlertIncidents.Add(incident); + newIncidents.Add(incident); + } + } + + // Resolve incidents no longer in Alertmanager + List resolvedIncidents = []; + foreach (AlertIncident incident in openIncidents) + { + if (liveFingerprints.Contains(incident.Fingerprint)) + { + // Still firing — update timestamp + incident.UpdatedAt = now; + } + else + { + // No longer in Alertmanager — resolve + incident.Status = IncidentStatus.Resolved; + incident.EndsAt ??= now; + incident.ResolvedAt = now; + incident.UpdatedAt = now; + resolvedIncidents.Add(incident); + } + } + + await db.SaveChangesAsync(ct); + + // Load cluster for notification dispatch and incident navigation property + KubernetesCluster? cluster = await db.KubernetesClusters.FindAsync([clusterId], ct); + if (cluster is null) return; + + // Dispatch notifications (need the saved incidents to have IDs) + List channels = await db.NotificationChannels + .Where(c => c.TenantId == cluster.TenantId && c.IsEnabled) + .ToListAsync(ct); + + if (channels.Count == 0) return; + + // Skip notifications while a maintenance window is active for this cluster/tenant + bool inMaintenance = await db.MaintenanceWindows.AnyAsync(w => + w.TenantId == cluster.TenantId + && w.StartsAt <= now + && w.EndsAt >= now + && (w.ClusterId == null || w.ClusterId == clusterId), ct); + + if (inMaintenance) + { + logger.LogInformation( + "Cluster {ClusterId} is in a maintenance window — skipping notifications for {New} new / {Resolved} resolved alerts", + clusterId, newIncidents.Count, resolvedIncidents.Count); + return; + } + + foreach (AlertIncident incident in newIncidents) + { + incident.Cluster = cluster!; + foreach (NotificationChannel channel in channels) + { + await notificationService.SendAsync(incident, channel, isFiring: true, ct); + } + } + + foreach (AlertIncident incident in resolvedIncidents) + { + incident.Cluster = cluster!; + foreach (NotificationChannel channel in channels) + { + await notificationService.SendAsync(incident, channel, isFiring: false, ct); + } + } + } +} diff --git a/src/EntKube.Web/Services/AppGovernanceService.cs b/src/EntKube.Web/Services/AppGovernanceService.cs new file mode 100644 index 0000000..4ebebba --- /dev/null +++ b/src/EntKube.Web/Services/AppGovernanceService.cs @@ -0,0 +1,444 @@ +using System.Text; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Manages app-level governance: namespace assignment, ResourceQuota, +/// NetworkPolicy, and RBAC (ServiceAccount/Role/RoleBinding). +/// +/// All governance data is stored in the database and can be applied to any +/// cluster where the app has deployments via ApplyToClusterAsync. +/// +public class AppGovernanceService( + IDbContextFactory dbFactory, + ILogger logger) +{ + // ── Namespace ──────────────────────────────────────────────────────────── + + public async Task SetNamespaceAsync( + Guid appId, string? ns, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + App? app = await db.Apps.FindAsync([appId], ct); + if (app is null) return false; + app.Namespace = string.IsNullOrWhiteSpace(ns) ? null : ns.Trim().ToLowerInvariant(); + await db.SaveChangesAsync(ct); + return true; + } + + // ── Quota ───────────────────────────────────────────────────────────────── + + public async Task GetQuotaAsync(Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AppQuotas.FirstOrDefaultAsync(q => q.AppId == appId, ct); + } + + public async Task SaveQuotaAsync( + Guid appId, + string? cpuRequest, string? cpuLimit, + string? memRequest, string? memLimit, + int? maxPods, int? maxPvcs, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppQuota? existing = await db.AppQuotas.FirstOrDefaultAsync(q => q.AppId == appId, ct); + + if (existing is null) + { + existing = new AppQuota { Id = Guid.NewGuid(), AppId = appId }; + db.AppQuotas.Add(existing); + } + + existing.CpuRequest = Blank(cpuRequest); + existing.CpuLimit = Blank(cpuLimit); + existing.MemoryRequest = Blank(memRequest); + existing.MemoryLimit = Blank(memLimit); + existing.MaxPods = maxPods; + existing.MaxPvcs = maxPvcs; + existing.UpdatedAt = DateTime.UtcNow; + + await db.SaveChangesAsync(ct); + return existing; + } + + public async Task DeleteQuotaAsync(Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppQuota? q = await db.AppQuotas.FirstOrDefaultAsync(x => x.AppId == appId, ct); + if (q is null) return false; + db.AppQuotas.Remove(q); + await db.SaveChangesAsync(ct); + return true; + } + + // ── Network policies ────────────────────────────────────────────────────── + + public async Task> GetNetworkPoliciesAsync( + Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AppNetworkPolicies + .Where(p => p.AppId == appId) + .OrderBy(p => p.Name) + .ToListAsync(ct); + } + + public async Task AddNetworkPolicyAsync( + Guid appId, string name, AppNetworkPolicyType type, + string? allowFromNs = null, string? customYaml = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppNetworkPolicy policy = new() + { + Id = Guid.NewGuid(), + AppId = appId, + Name = name.Trim().ToLowerInvariant(), + PolicyType = type, + AllowFromNamespace = Blank(allowFromNs), + CustomYaml = customYaml + }; + db.AppNetworkPolicies.Add(policy); + await db.SaveChangesAsync(ct); + return policy; + } + + public async Task DeleteNetworkPolicyAsync(Guid policyId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppNetworkPolicy? p = await db.AppNetworkPolicies.FindAsync([policyId], ct); + if (p is null) return false; + db.AppNetworkPolicies.Remove(p); + await db.SaveChangesAsync(ct); + return true; + } + + // ── RBAC ────────────────────────────────────────────────────────────────── + + public async Task GetRbacPolicyAsync(Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AppRbacPolicies + .Include(p => p.Rules) + .FirstOrDefaultAsync(p => p.AppId == appId, ct); + } + + public async Task SaveRbacPolicyAsync( + Guid appId, string serviceAccountName, bool autoMount, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppRbacPolicy? existing = await db.AppRbacPolicies + .FirstOrDefaultAsync(p => p.AppId == appId, ct); + + if (existing is null) + { + existing = new AppRbacPolicy + { + Id = Guid.NewGuid(), + AppId = appId, + ServiceAccountName = serviceAccountName.Trim().ToLowerInvariant() + }; + db.AppRbacPolicies.Add(existing); + } + else + { + existing.ServiceAccountName = serviceAccountName.Trim().ToLowerInvariant(); + } + + existing.AutoMountToken = autoMount; + await db.SaveChangesAsync(ct); + return existing; + } + + public async Task AddRbacRuleAsync( + Guid rbacPolicyId, string apiGroups, string resources, string verbs, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppRbacRule rule = new() + { + Id = Guid.NewGuid(), + AppRbacPolicyId = rbacPolicyId, + ApiGroups = apiGroups.Trim(), + Resources = resources.Trim(), + Verbs = verbs.Trim() + }; + db.AppRbacRules.Add(rule); + await db.SaveChangesAsync(ct); + return rule; + } + + public async Task DeleteRbacRuleAsync(Guid ruleId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppRbacRule? r = await db.AppRbacRules.FindAsync([ruleId], ct); + if (r is null) return false; + db.AppRbacRules.Remove(r); + await db.SaveChangesAsync(ct); + return true; + } + + public async Task DeleteRbacPolicyAsync(Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AppRbacPolicy? p = await db.AppRbacPolicies.FirstOrDefaultAsync(x => x.AppId == appId, ct); + if (p is null) return false; + db.AppRbacPolicies.Remove(p); + await db.SaveChangesAsync(ct); + return true; + } + + // ── Load all governance for an app ──────────────────────────────────────── + + public async Task LoadAsync(Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + App? app = await db.Apps.FindAsync([appId], ct); + + return new AppGovernanceData + { + Namespace = app?.Namespace, + Quota = await GetQuotaAsync(appId, ct), + NetworkPolicies = await GetNetworkPoliciesAsync(appId, ct), + RbacPolicy = await GetRbacPolicyAsync(appId, ct) + }; + } + + // ── K8s apply ───────────────────────────────────────────────────────────── + + /// + /// Applies all governance resources (Namespace, ResourceQuota, NetworkPolicies, + /// ServiceAccount/Role/RoleBinding) to the given cluster using kubectl apply. + /// Idempotent — safe to run multiple times. + /// + public async Task<(bool Success, string Output)> ApplyToClusterAsync( + Guid appId, KubernetesCluster cluster, CancellationToken ct = default) + { + AppGovernanceData data = await LoadAsync(appId, ct); + + if (string.IsNullOrWhiteSpace(data.Namespace)) + return (false, "App has no namespace configured. Set a namespace before applying."); + + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return (false, "Cluster has no kubeconfig configured."); + + string yaml = BuildManifest(data); + string kubeconfigPath = Path.Combine(Path.GetTempPath(), $"entkube-gov-{Guid.NewGuid():N}.kubeconfig"); + string manifestPath = Path.Combine(Path.GetTempPath(), $"entkube-gov-{Guid.NewGuid():N}.yaml"); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, cluster.Kubeconfig, ct); + await File.WriteAllTextAsync(manifestPath, yaml, ct); + + System.Diagnostics.ProcessStartInfo psi = new("kubectl", + $"apply -f {manifestPath} --kubeconfig {kubeconfigPath}") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using System.Diagnostics.Process proc = new() { StartInfo = psi }; + StringBuilder output = new(); + proc.OutputDataReceived += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); }; + proc.ErrorDataReceived += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); }; + proc.Start(); + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + await proc.WaitForExitAsync(ct); + + bool ok = proc.ExitCode == 0; + string result = output.ToString().TrimEnd(); + + if (ok) + logger.LogInformation("Governance applied to {Cluster}/{Namespace}", cluster.Name, data.Namespace); + else + logger.LogWarning("Governance apply failed for {Cluster}: {Output}", cluster.Name, result); + + return (ok, result); + } + finally + { + if (File.Exists(kubeconfigPath)) File.Delete(kubeconfigPath); + if (File.Exists(manifestPath)) File.Delete(manifestPath); + } + } + + // ── Manifest builder ────────────────────────────────────────────────────── + + public static string BuildManifest(AppGovernanceData data) + { + var docs = new List(); + string ns = data.Namespace!; + + // 1. Namespace + docs.Add(Y( + "apiVersion: v1", + "kind: Namespace", + "metadata:", + $" name: {ns}")); + + // 2. ResourceQuota + if (data.Quota is { } q) + { + var hard = new StringBuilder(); + if (!string.IsNullOrWhiteSpace(q.CpuRequest)) hard.AppendLine($" requests.cpu: \"{q.CpuRequest}\""); + if (!string.IsNullOrWhiteSpace(q.CpuLimit)) hard.AppendLine($" limits.cpu: \"{q.CpuLimit}\""); + if (!string.IsNullOrWhiteSpace(q.MemoryRequest)) hard.AppendLine($" requests.memory: \"{q.MemoryRequest}\""); + if (!string.IsNullOrWhiteSpace(q.MemoryLimit)) hard.AppendLine($" limits.memory: \"{q.MemoryLimit}\""); + if (q.MaxPods.HasValue) hard.AppendLine($" pods: \"{q.MaxPods}\""); + if (q.MaxPvcs.HasValue) hard.AppendLine($" persistentvolumeclaims: \"{q.MaxPvcs}\""); + + if (hard.Length > 0) + { + docs.Add(Y( + "apiVersion: v1", + "kind: ResourceQuota", + "metadata:", + $" name: app-quota", + $" namespace: {ns}", + "spec:", + " hard:", + hard.ToString().TrimEnd())); + } + } + + // 3. NetworkPolicies + foreach (AppNetworkPolicy np in data.NetworkPolicies) + { + string header = Y( + "apiVersion: networking.k8s.io/v1", + "kind: NetworkPolicy", + "metadata:", + $" name: {np.Name}", + $" namespace: {ns}", + "spec:", + " podSelector: {}"); + + string policyYaml = np.PolicyType switch + { + AppNetworkPolicyType.DenyAll => + header + "\n policyTypes:\n - Ingress\n - Egress", + + AppNetworkPolicyType.AllowFromIngress => + header + Y( + " ingress:", + " - from:", + " - namespaceSelector:", + " matchExpressions:", + " - key: kubernetes.io/metadata.name", + " operator: In", + " values:", + " - ingress-nginx", + " - traefik"), + + AppNetworkPolicyType.AllowFromSameNamespace => + header + Y( + " ingress:", + " - from:", + " - podSelector: {}"), + + AppNetworkPolicyType.AllowFromNamespace + when !string.IsNullOrWhiteSpace(np.AllowFromNamespace) => + header + Y( + " ingress:", + " - from:", + " - namespaceSelector:", + " matchLabels:", + $" kubernetes.io/metadata.name: {np.AllowFromNamespace}"), + + AppNetworkPolicyType.Custom when !string.IsNullOrWhiteSpace(np.CustomYaml) => + np.CustomYaml, + + _ => string.Empty + }; + + if (!string.IsNullOrWhiteSpace(policyYaml)) + docs.Add(policyYaml.Trim()); + } + + // 4. ServiceAccount + Role + RoleBinding + if (data.RbacPolicy is { } rbac && rbac.Rules.Count > 0) + { + string sa = rbac.ServiceAccountName; + + docs.Add(Y( + "apiVersion: v1", + "kind: ServiceAccount", + "metadata:", + $" name: {sa}", + $" namespace: {ns}", + $"automountServiceAccountToken: {rbac.AutoMountToken.ToString().ToLowerInvariant()}")); + + var rulesYaml = new StringBuilder(); + foreach (AppRbacRule rule in rbac.Rules) + { + string apiGroupsYaml = string.Join(", ", + (string.IsNullOrWhiteSpace(rule.ApiGroups) + ? [""] + : rule.ApiGroups.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Select(g => $"\"{g.Trim()}\"")); + + string resourcesYaml = string.Join(", ", + rule.Resources.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(r => $"\"{r.Trim()}\"")); + + string verbsYaml = string.Join(", ", + rule.Verbs.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(v => $"\"{v.Trim()}\"")); + + rulesYaml.AppendLine($" - apiGroups: [{apiGroupsYaml}]"); + rulesYaml.AppendLine($" resources: [{resourcesYaml}]"); + rulesYaml.AppendLine($" verbs: [{verbsYaml}]"); + } + + docs.Add(Y( + "apiVersion: rbac.authorization.k8s.io/v1", + "kind: Role", + "metadata:", + $" name: {sa}-role", + $" namespace: {ns}", + "rules:", + rulesYaml.ToString().TrimEnd())); + + docs.Add(Y( + "apiVersion: rbac.authorization.k8s.io/v1", + "kind: RoleBinding", + "metadata:", + $" name: {sa}-binding", + $" namespace: {ns}", + "subjects:", + " - kind: ServiceAccount", + $" name: {sa}", + $" namespace: {ns}", + "roleRef:", + " kind: Role", + $" name: {sa}-role", + " apiGroup: rbac.authorization.k8s.io")); + } + + return string.Join("\n---\n", docs); + } + + // Join lines into a YAML document string. + private static string Y(params string[] lines) => string.Join("\n", lines); + + private static string? Blank(string? s) => + string.IsNullOrWhiteSpace(s) ? null : s.Trim(); +} + +/// Snapshot of all governance data for an app. +public class AppGovernanceData +{ + public string? Namespace { get; init; } + public AppQuota? Quota { get; init; } + public List NetworkPolicies { get; init; } = []; + public AppRbacPolicy? RbacPolicy { get; init; } +} diff --git a/src/EntKube.Web/Services/AppOfAppsService.cs b/src/EntKube.Web/Services/AppOfAppsService.cs new file mode 100644 index 0000000..82bd759 --- /dev/null +++ b/src/EntKube.Web/Services/AppOfAppsService.cs @@ -0,0 +1,329 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace EntKube.Web.Services; + +/// +/// Parses ArgoCD Application CRD YAML files and reconciles child AppDeployment rows. +/// +/// Supported input: one or more YAML documents, each containing an ArgoCD Application +/// CRD (apiVersion: argoproj.io/v1alpha1, kind: Application). The service maps each +/// Application to an AppDeployment under the parent app and ensures the set of child +/// deployments matches the set of Applications in Git — creating new ones, updating +/// changed ones, and deleting removed ones. +/// +/// CRD → AppDeployment field mapping: +/// metadata.name → AppDeployment.Name +/// spec.source.repoURL → resolved to GitRepository by URL +/// spec.source.targetRevision → GitRevision (fallback: repo DefaultBranch) +/// spec.source.path → GitPath +/// spec.source.helm → DeploymentType = GitHelm, HelmValues from valuesFiles/values +/// spec.destination.namespace → Namespace +/// spec.destination.server → matched to KubernetesCluster by ApiServerUrl +/// +public class AppOfAppsService( + IDbContextFactory dbFactory, + ILogger logger) +{ + private static readonly IDeserializer Yaml = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + /// + /// Reconciles child deployments for a GitAppOfApps deployment from the parsed + /// YAML files. Returns the number of deployments created/updated/deleted. + /// + public async Task ReconcileAsync( + AppDeployment parent, + Dictionary yamlFiles, + CancellationToken ct = default) + { + List applications = ParseApplications(yamlFiles); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Load existing child deployments for this parent. + List existing = await db.AppDeployments + .Where(d => d.ParentDeploymentId == parent.Id) + .ToListAsync(ct); + + // Load repos and clusters for this tenant so we can resolve references. + Guid tenantId = await db.Apps + .Where(a => a.Id == parent.AppId) + .Select(a => a.Customer.TenantId) + .FirstAsync(ct); + + List repos = await db.GitRepositories + .Where(r => r.TenantId == tenantId) + .ToListAsync(ct); + + List clusters = await db.KubernetesClusters + .Where(c => c.TenantId == tenantId) + .ToListAsync(ct); + + // Load the environment for the parent deployment to use as default. + Guid defaultEnvId = parent.EnvironmentId; + Guid defaultClusterId = parent.ClusterId; + + int created = 0, updated = 0, deleted = 0; + + HashSet seenNames = []; + + foreach (ArgoCdApplication app in applications) + { + seenNames.Add(app.Metadata.Name); + + GitRepository? repo = ResolveRepo(repos, app.Spec.Source.RepoUrl); + KubernetesCluster? cluster = ResolveCluster(clusters, app.Spec.Destination.Server) + ?? clusters.FirstOrDefault(c => c.Id == defaultClusterId); + + if (repo is null) + { + logger.LogWarning( + "AppOfApps {Parent}: no registered repo matches URL '{Url}' for child '{Child}' — skipping.", + parent.Name, app.Spec.Source.RepoUrl, app.Metadata.Name); + continue; + } + + if (cluster is null) + { + logger.LogWarning( + "AppOfApps {Parent}: no cluster matches server '{Server}' for child '{Child}' — skipping.", + parent.Name, app.Spec.Destination.Server, app.Metadata.Name); + continue; + } + + AppDeployment? child = existing.FirstOrDefault(d => d.Name == app.Metadata.Name); + + DeploymentType type = app.Spec.Source.Helm is not null ? DeploymentType.GitHelm : DeploymentType.GitYaml; + string revision = app.Spec.Source.TargetRevision ?? repo.DefaultBranch; + string ns = app.Spec.Destination.Namespace ?? "default"; + string path = app.Spec.Source.Path ?? "."; + + if (child is null) + { + child = new AppDeployment + { + Id = Guid.NewGuid(), + AppId = parent.AppId, + Name = app.Metadata.Name, + Type = type, + EnvironmentId = defaultEnvId, + ClusterId = cluster.Id, + Namespace = ns, + GitRepositoryId = repo.Id, + GitPath = path, + GitRevision = revision, + GitAutoSync = true, + ParentDeploymentId = parent.Id + }; + + if (app.Spec.Source.Helm is not null) + { + child.HelmValues = BuildHelmValues(app.Spec.Source.Helm); + } + + db.AppDeployments.Add(child); + created++; + } + else + { + bool changed = child.Type != type + || child.ClusterId != cluster.Id + || child.Namespace != ns + || child.GitRepositoryId != repo.Id + || child.GitPath != path + || child.GitRevision != revision; + + if (changed) + { + child.Type = type; + child.ClusterId = cluster.Id; + child.Namespace = ns; + child.GitRepositoryId = repo.Id; + child.GitPath = path; + child.GitRevision = revision; + + if (app.Spec.Source.Helm is not null) + child.HelmValues = BuildHelmValues(app.Spec.Source.Helm); + + updated++; + } + } + } + + // Delete child deployments whose Application was removed from Git. + foreach (AppDeployment orphan in existing.Where(d => !seenNames.Contains(d.Name))) + { + db.AppDeployments.Remove(orphan); + deleted++; + logger.LogInformation( + "AppOfApps {Parent}: removing child '{Child}' (no longer in Git).", + parent.Name, orphan.Name); + } + + await db.SaveChangesAsync(ct); + + return new AppOfAppsReconcileResult + { + Created = created, + Updated = updated, + Deleted = deleted + }; + } + + // ── Parsing ────────────────────────────────────────────────────────────────── + + private List ParseApplications(Dictionary files) + { + List result = []; + + foreach ((string filePath, string content) in files) + { + if (!filePath.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) + && !filePath.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)) + continue; + + // A single file may contain multiple YAML documents separated by "---". + foreach (string document in SplitDocuments(content)) + { + try + { + ArgoCdApplication? app = Yaml.Deserialize(document); + + if (app?.ApiVersion?.Contains("argoproj.io") == true + && app.Kind == "Application" + && app.Metadata?.Name is not null + && app.Spec?.Source is not null + && app.Spec.Destination is not null) + { + result.Add(app); + } + } + catch (YamlException ex) + { + logger.LogWarning(ex, "Failed to parse YAML document in {File}", filePath); + } + } + } + + return result; + } + + private static IEnumerable SplitDocuments(string yaml) + { + // Split on lines that are exactly "---" (document separator). + string[] parts = yaml.Split("\n---", StringSplitOptions.RemoveEmptyEntries); + foreach (string part in parts) + { + string trimmed = part.Trim(); + if (!string.IsNullOrWhiteSpace(trimmed)) + yield return trimmed; + } + } + + private static GitRepository? ResolveRepo(List repos, string? repoUrl) + { + if (string.IsNullOrEmpty(repoUrl)) return null; + + // Exact match first, then normalised comparison (trailing slashes, .git suffix). + return repos.FirstOrDefault(r => string.Equals(r.Url, repoUrl, StringComparison.OrdinalIgnoreCase)) + ?? repos.FirstOrDefault(r => NormalizeUrl(r.Url) == NormalizeUrl(repoUrl)); + } + + private static KubernetesCluster? ResolveCluster(List clusters, string? server) + { + if (string.IsNullOrEmpty(server)) return null; + + // ArgoCD uses "https://kubernetes.default.svc" to mean in-cluster. + if (server.Contains("kubernetes.default")) + return clusters.FirstOrDefault(); + + return clusters.FirstOrDefault(c => + string.Equals(c.ApiServerUrl, server, StringComparison.OrdinalIgnoreCase)); + } + + private static string NormalizeUrl(string url) => + url.TrimEnd('/').TrimEnd(".git".ToCharArray()).ToLowerInvariant(); + + private static string? BuildHelmValues(ArgoCdHelmSource helm) + { + // Merge inline values object into a flat YAML string for HelmValues field. + if (helm.Values is not null) return helm.Values; + + if (helm.Parameters?.Count > 0) + { + var lines = helm.Parameters.Select(p => $"{p.Name}: {p.Value}"); + return string.Join("\n", lines); + } + + return null; + } +} + +// ── DTOs for deserializing ArgoCD Application CRD ──────────────────────────── + +#pragma warning disable CS8618 +internal class ArgoCdApplication +{ + public string ApiVersion { get; set; } + public string Kind { get; set; } + public ArgoCdMetadata Metadata { get; set; } + public ArgoCdApplicationSpec Spec { get; set; } +} + +internal class ArgoCdMetadata +{ + public string Name { get; set; } + public string? Namespace { get; set; } +} + +internal class ArgoCdApplicationSpec +{ + public ArgoCdSource Source { get; set; } + public ArgoCdDestination Destination { get; set; } + public string? Project { get; set; } +} + +internal class ArgoCdSource +{ + public string? RepoUrl { get; set; } + public string? TargetRevision { get; set; } + public string? Path { get; set; } + public ArgoCdHelmSource? Helm { get; set; } +} + +internal class ArgoCdDestination +{ + public string? Server { get; set; } + public string? Namespace { get; set; } + public string? Name { get; set; } +} + +internal class ArgoCdHelmSource +{ + public string? Values { get; set; } + public string? ValueFiles { get; set; } + public List? Parameters { get; set; } +} + +internal class ArgoCdHelmParameter +{ + public string Name { get; set; } + public string? Value { get; set; } +} +#pragma warning restore CS8618 + +public class AppOfAppsReconcileResult +{ + public int Created { get; init; } + public int Updated { get; init; } + public int Deleted { get; init; } + + public override string ToString() => + $"created={Created} updated={Updated} deleted={Deleted}"; +} diff --git a/src/EntKube.Web/Services/AuditService.cs b/src/EntKube.Web/Services/AuditService.cs new file mode 100644 index 0000000..958b228 --- /dev/null +++ b/src/EntKube.Web/Services/AuditService.cs @@ -0,0 +1,53 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Writes and retrieves AuditEvents. Injected into services that perform +/// destructive operations so there is always a human-readable activity +/// timeline attached to every deployment. +/// +public class AuditService(IDbContextFactory dbFactory) +{ + public async Task RecordAsync( + Guid? deploymentId, + string action, + string resourceKind, + string? resourceName = null, + string? details = null, + string? performedBy = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + db.AuditEvents.Add(new AuditEvent + { + Id = Guid.NewGuid(), + DeploymentId = deploymentId, + Action = action, + ResourceKind = resourceKind, + ResourceName = resourceName, + Details = details, + PerformedBy = performedBy, + OccurredAt = DateTime.UtcNow + }); + + await db.SaveChangesAsync(ct); + } + + /// + /// Returns the most recent audit events for a deployment, newest first. + /// + public async Task> GetDeploymentEventsAsync( + Guid deploymentId, int limit = 50, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.AuditEvents + .Where(a => a.DeploymentId == deploymentId) + .OrderByDescending(a => a.OccurredAt) + .Take(limit) + .ToListAsync(ct); + } +} diff --git a/src/EntKube.Web/Services/BackupService.cs b/src/EntKube.Web/Services/BackupService.cs new file mode 100644 index 0000000..5493d9f --- /dev/null +++ b/src/EntKube.Web/Services/BackupService.cs @@ -0,0 +1,373 @@ +using System.IO.Compression; +using System.Text.Json; +using System.Text.Json.Serialization; +using EntKube.Web.Data; +using EntKube.Web.Data.Backup; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; + +namespace EntKube.Web.Services; + +/// +/// Exports the full EntKube app state to a gzip-compressed JSON bundle and +/// restores it on a target installation. Secrets are decrypted during export +/// and re-encrypted with fresh DEKs on restore — the bundle does not carry +/// the root key, but it does carry plaintext secret values, so treat it as +/// sensitive material (encrypt at rest, restrict access). +/// +public class BackupService( + IDbContextFactory dbFactory, + VaultEncryptionService encryption, + ILogger logger) +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + }; + + // ── Export ────────────────────────────────────────────────────────────── + + public async Task ExportAsync(string performedBy) + { + await using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Unseal every tenant's DEK so we can decrypt individual secrets. + Dictionary dekByVaultId = []; + List vaultRecords = []; + + foreach (SecretVault vault in await db.SecretVaults.AsNoTracking().ToListAsync()) + { + byte[] dek = encryption.UnsealDataKey(vault.EncryptedDataKey, vault.Nonce); + dekByVaultId[vault.Id] = dek; + vaultRecords.Add(new VaultRecord { Id = vault.Id, TenantId = vault.TenantId, CreatedAt = vault.CreatedAt }); + } + + List secretRecords = []; + foreach (VaultSecret s in await db.VaultSecrets.AsNoTracking().ToListAsync()) + { + if (!dekByVaultId.TryGetValue(s.VaultId, out byte[]? dek)) + { + logger.LogWarning("VaultSecret {Id} references unknown vault {VaultId} — skipping.", s.Id, s.VaultId); + continue; + } + string plaintext = encryption.Decrypt(dek, s.EncryptedValue, s.Nonce); + secretRecords.Add(new VaultSecretRecord( + s.Id, s.VaultId, s.Name, plaintext, + s.AppId, s.ComponentId, s.StorageLinkId, s.OpenStackConnectionId, + s.CnpgClusterId, s.CnpgDatabaseId, s.MongoDatabaseId, s.MongoClusterId, + s.RegisteredPostgresDatabaseId, s.RabbitMQClusterId, + s.SyncToKubernetes, s.KubernetesClusterId, s.KubernetesSecretName, s.KubernetesNamespace, + s.CreatedAt, s.UpdatedAt)); + } + + List credRecords = []; + foreach (DockerRegistryCredential c in await db.DockerRegistryCredentials.AsNoTracking().ToListAsync()) + { + if (!dekByVaultId.TryGetValue(c.VaultId, out byte[]? dek)) + { + logger.LogWarning("DockerCredential {Id} references unknown vault {VaultId} — skipping.", c.Id, c.VaultId); + continue; + } + string plainPassword = encryption.Decrypt(dek, c.EncryptedPassword, c.PasswordNonce); + credRecords.Add(new DockerCredentialRecord( + c.Id, c.VaultId, c.Name, c.RegistryType, c.Server, c.Username, plainPassword, + c.Email, c.AppId, c.KubernetesClusterId, c.KubernetesSecretName, c.KubernetesNamespace, + c.CreatedAt, c.UpdatedAt)); + } + + List userRecords = (await db.Users.AsNoTracking().ToListAsync()) + .Select(u => new UserRecord( + u.Id, u.UserName, u.Email, u.PasswordHash, u.EmailConfirmed, + u.NormalizedUserName, u.NormalizedEmail, u.SecurityStamp, u.ConcurrencyStamp, + u.PhoneNumber, u.PhoneNumberConfirmed, u.TwoFactorEnabled, + u.LockoutEnd, u.LockoutEnabled, u.AccessFailedCount)) + .ToList(); + + List roleRecords = (await db.Roles.AsNoTracking().ToListAsync()) + .Select(r => new RoleRecord(r.Id, r.Name, r.NormalizedName, r.ConcurrencyStamp)) + .ToList(); + + List userRoleRecords = (await db.Set>().AsNoTracking().ToListAsync()) + .Select(ur => new UserRoleRecord(ur.UserId, ur.RoleId)) + .ToList(); + + BackupBundle bundle = new() + { + CreatedBy = performedBy, + CreatedAt = DateTime.UtcNow, + Users = userRecords, + Roles = roleRecords, + UserRoles = userRoleRecords, + Tenants = await db.Tenants.AsNoTracking().ToListAsync(), + TenantRoles = await db.TenantRoles.AsNoTracking().ToListAsync(), + TenantMemberships = await db.TenantMemberships.AsNoTracking().ToListAsync(), + Groups = await db.Groups.AsNoTracking().ToListAsync(), + GroupMemberships = await db.GroupMemberships.AsNoTracking().ToListAsync(), + Environments = await db.Environments.AsNoTracking().ToListAsync(), + Customers = await db.Customers.AsNoTracking().ToListAsync(), + CustomerAccesses = await db.CustomerAccesses.AsNoTracking().ToListAsync(), + Apps = await db.Apps.AsNoTracking().ToListAsync(), + AppEnvironments = await db.AppEnvironments.AsNoTracking().ToListAsync(), + KubernetesClusters = await db.KubernetesClusters.AsNoTracking().ToListAsync(), + ClusterComponents = await db.ClusterComponents.AsNoTracking().ToListAsync(), + ExternalRoutes = await db.ExternalRoutes.AsNoTracking().ToListAsync(), + OpenStackConnections = await db.OpenStackConnections.AsNoTracking().ToListAsync(), + StorageLinks = await db.StorageLinks.AsNoTracking().ToListAsync(), + AppDeployments = await db.AppDeployments.AsNoTracking().ToListAsync(), + DeploymentManifests = await db.DeploymentManifests.AsNoTracking().ToListAsync(), + StorageBindings = await db.StorageBindings.AsNoTracking().ToListAsync(), + CnpgClusters = await db.CnpgClusters.AsNoTracking().ToListAsync(), + CnpgDatabases = await db.CnpgDatabases.AsNoTracking().ToListAsync(), + MongoClusters = await db.MongoClusters.AsNoTracking().ToListAsync(), + MongoDatabases = await db.MongoDatabases.AsNoTracking().ToListAsync(), + RabbitMQClusters = await db.RabbitMQClusters.AsNoTracking().ToListAsync(), + RegisteredPostgresInstances = await db.RegisteredPostgresInstances.AsNoTracking().ToListAsync(), + RegisteredPostgresDatabases = await db.RegisteredPostgresDatabases.AsNoTracking().ToListAsync(), + DatabaseBindings = await db.DatabaseBindings.AsNoTracking().ToListAsync(), + MessagingBindings = await db.MessagingBindings.AsNoTracking().ToListAsync(), + KeycloakComponentConfigs = await db.KeycloakComponentConfigs.AsNoTracking().ToListAsync(), + KeycloakThemes = await db.KeycloakThemes.AsNoTracking().ToListAsync(), + KeycloakRealms = await db.KeycloakRealms.AsNoTracking().ToListAsync(), + HarborComponentConfigs = await db.HarborComponentConfigs.AsNoTracking().ToListAsync(), + HarborProjects = await db.HarborProjects.AsNoTracking().ToListAsync(), + NotificationChannels = await db.NotificationChannels.AsNoTracking().ToListAsync(), + SlaTargets = await db.SlaTargets.AsNoTracking().ToListAsync(), + MaintenanceWindows = await db.MaintenanceWindows.AsNoTracking().ToListAsync(), + SecretVaults = vaultRecords, + VaultSecrets = secretRecords, + DockerCredentials = credRecords, + }; + + using MemoryStream ms = new(); + await using (GZipStream gz = new(ms, CompressionLevel.Optimal, leaveOpen: true)) + await JsonSerializer.SerializeAsync(gz, bundle, JsonOptions); + + logger.LogInformation("Backup exported by {User}: {Tenants} tenants, {Secrets} secrets, {Creds} registry credentials.", + performedBy, bundle.Tenants.Count, bundle.VaultSecrets.Count, bundle.DockerCredentials.Count); + + return ms.ToArray(); + } + + // ── Import ────────────────────────────────────────────────────────────── + + /// + /// Restores a backup bundle. When is true, + /// ALL existing data is deleted before the import begins (the wipe and the + /// insert are wrapped in a single transaction so a failed import rolls back + /// the wipe too). Designed for PostgreSQL; the TRUNCATE CASCADE syntax is + /// Postgres-specific. + /// + public async Task ImportAsync(Stream bundleStream, bool wipeExisting) + { + await using GZipStream gz = new(bundleStream, CompressionMode.Decompress); + BackupBundle? bundle = await JsonSerializer.DeserializeAsync(gz, JsonOptions) + ?? throw new InvalidDataException("Failed to deserialize backup bundle."); + + if (bundle.Version != 1) + throw new InvalidDataException($"Unsupported backup version: {bundle.Version}. Only version 1 is supported."); + + await using ApplicationDbContext db = dbFactory.CreateDbContext(); + await using IDbContextTransaction tx = await db.Database.BeginTransactionAsync(); + + try + { + if (wipeExisting) + { + logger.LogInformation("Wiping all existing data before restore."); + // TRUNCATE ... CASCADE removes the listed tables and all tables that + // transitively reference them via FK constraints, regardless of ON DELETE + // behaviour. Three top-level tables cover the entire schema: + // Tenants → every tenant-owned entity (clusters, apps, secrets, …) + // AspNetUsers → memberships, customer accesses, group memberships + // AspNetRoles → role assignments + await db.Database.ExecuteSqlRawAsync(@"TRUNCATE ""Tenants"" CASCADE"); + await db.Database.ExecuteSqlRawAsync(@"TRUNCATE ""AspNetUsers"" CASCADE"); + await db.Database.ExecuteSqlRawAsync(@"TRUNCATE ""AspNetRoles"" CASCADE"); + db.ChangeTracker.Clear(); + } + + // Identity — insert roles and users first so FK references from + // TenantMembership, CustomerAccess, etc. can resolve. + await InsertBatch(db, bundle.Roles.Select(r => new IdentityRole + { + Id = r.Id, Name = r.Name, NormalizedName = r.NormalizedName, ConcurrencyStamp = r.ConcurrencyStamp, + })); + + await InsertBatch(db, bundle.Users.Select(u => new ApplicationUser + { + Id = u.Id, UserName = u.UserName, Email = u.Email, PasswordHash = u.PasswordHash, + EmailConfirmed = u.EmailConfirmed, NormalizedUserName = u.NormalizedUserName, + NormalizedEmail = u.NormalizedEmail, SecurityStamp = u.SecurityStamp, + ConcurrencyStamp = u.ConcurrencyStamp, PhoneNumber = u.PhoneNumber, + PhoneNumberConfirmed = u.PhoneNumberConfirmed, TwoFactorEnabled = u.TwoFactorEnabled, + LockoutEnd = u.LockoutEnd, LockoutEnabled = u.LockoutEnabled, AccessFailedCount = u.AccessFailedCount, + })); + + await InsertBatch(db, bundle.UserRoles.Select(ur => + new IdentityUserRole { UserId = ur.UserId, RoleId = ur.RoleId })); + + // Tenant structure — parents before children. + await InsertEntities(db, db.Tenants, bundle.Tenants); + await InsertEntities(db, db.TenantRoles, bundle.TenantRoles); + await InsertEntities(db, db.TenantMemberships, bundle.TenantMemberships); + await InsertEntities(db, db.Groups, bundle.Groups); + await InsertEntities(db, db.GroupMemberships, bundle.GroupMemberships); + await InsertEntities(db, db.Environments, bundle.Environments); + await InsertEntities(db, db.Customers, bundle.Customers); + await InsertEntities(db, db.CustomerAccesses, bundle.CustomerAccesses); + await InsertEntities(db, db.Apps, bundle.Apps); + await InsertEntities(db, db.AppEnvironments, bundle.AppEnvironments); + + // Infrastructure — OpenStack and K8s clusters before storage links + // (StorageLink.OpenStackConnectionId and StorageLink.ComponentId are optional + // FKs that must exist when non-null). + await InsertEntities(db, db.OpenStackConnections, bundle.OpenStackConnections); + await InsertEntities(db, db.KubernetesClusters, bundle.KubernetesClusters); + await InsertEntities(db, db.ClusterComponents, bundle.ClusterComponents); + await InsertEntities(db, db.ExternalRoutes, bundle.ExternalRoutes); + await InsertEntities(db, db.StorageLinks, bundle.StorageLinks); + + // Deployments — apps and clusters must exist first (RESTRICT FKs). + await InsertEntities(db, db.AppDeployments, bundle.AppDeployments); + await InsertEntities(db, db.DeploymentManifests, bundle.DeploymentManifests); + await InsertEntities(db, db.StorageBindings, bundle.StorageBindings); + + // Databases — after storage links and k8s clusters. + await InsertEntities(db, db.RegisteredPostgresInstances, bundle.RegisteredPostgresInstances); + await InsertEntities(db, db.RegisteredPostgresDatabases, bundle.RegisteredPostgresDatabases); + await InsertEntities(db, db.CnpgClusters, bundle.CnpgClusters); + await InsertEntities(db, db.CnpgDatabases, bundle.CnpgDatabases); + await InsertEntities(db, db.MongoClusters, bundle.MongoClusters); + await InsertEntities(db, db.MongoDatabases, bundle.MongoDatabases); + await InsertEntities(db, db.RabbitMQClusters, bundle.RabbitMQClusters); + await InsertEntities(db, db.DatabaseBindings, bundle.DatabaseBindings); + await InsertEntities(db, db.MessagingBindings, bundle.MessagingBindings); + + // Identity/Auth providers — after cluster components and databases. + await InsertEntities(db, db.KeycloakComponentConfigs, bundle.KeycloakComponentConfigs); + await InsertEntities(db, db.KeycloakThemes, bundle.KeycloakThemes); + await InsertEntities(db, db.KeycloakRealms, bundle.KeycloakRealms); + + // Container registry. + await InsertEntities(db, db.HarborComponentConfigs, bundle.HarborComponentConfigs); + await InsertEntities(db, db.HarborProjects, bundle.HarborProjects); + + // Operations. + await InsertEntities(db, db.NotificationChannels, bundle.NotificationChannels); + await InsertEntities(db, db.SlaTargets, bundle.SlaTargets); + await InsertEntities(db, db.MaintenanceWindows, bundle.MaintenanceWindows); + + // Secrets — generate a fresh DEK for every vault and re-encrypt all + // secrets with it. The new DEK is sealed with the new server's root key. + Dictionary newDekByVaultId = []; + + foreach (VaultRecord vr in bundle.SecretVaults) + { + byte[] dek = encryption.GenerateDataKey(); + (byte[] encKey, byte[] nonce) = encryption.SealDataKey(dek); + newDekByVaultId[vr.Id] = dek; + db.SecretVaults.Add(new SecretVault + { + Id = vr.Id, TenantId = vr.TenantId, + EncryptedDataKey = encKey, Nonce = nonce, + CreatedAt = vr.CreatedAt, + }); + } + if (bundle.SecretVaults.Count > 0) + { + await db.SaveChangesAsync(); + db.ChangeTracker.Clear(); + } + + foreach (VaultSecretRecord sr in bundle.VaultSecrets) + { + if (!newDekByVaultId.TryGetValue(sr.VaultId, out byte[]? dek)) + { + logger.LogWarning("VaultSecret {Id} vault {VaultId} not in bundle — skipping.", sr.Id, sr.VaultId); + continue; + } + (byte[] ciphertext, byte[] sNonce) = encryption.Encrypt(dek, sr.PlaintextValue); + db.VaultSecrets.Add(new VaultSecret + { + Id = sr.Id, VaultId = sr.VaultId, Name = sr.Name, + EncryptedValue = ciphertext, Nonce = sNonce, + AppId = sr.AppId, ComponentId = sr.ComponentId, StorageLinkId = sr.StorageLinkId, + OpenStackConnectionId = sr.OpenStackConnectionId, CnpgClusterId = sr.CnpgClusterId, + CnpgDatabaseId = sr.CnpgDatabaseId, MongoDatabaseId = sr.MongoDatabaseId, + MongoClusterId = sr.MongoClusterId, RegisteredPostgresDatabaseId = sr.RegisteredPostgresDatabaseId, + RabbitMQClusterId = sr.RabbitMQClusterId, + SyncToKubernetes = sr.SyncToKubernetes, KubernetesClusterId = sr.KubernetesClusterId, + KubernetesSecretName = sr.KubernetesSecretName, KubernetesNamespace = sr.KubernetesNamespace, + CreatedAt = sr.CreatedAt, UpdatedAt = sr.UpdatedAt, + }); + } + if (bundle.VaultSecrets.Count > 0) + { + await db.SaveChangesAsync(); + db.ChangeTracker.Clear(); + } + + foreach (DockerCredentialRecord cr in bundle.DockerCredentials) + { + if (!newDekByVaultId.TryGetValue(cr.VaultId, out byte[]? dek)) + { + logger.LogWarning("DockerCredential {Id} vault {VaultId} not in bundle — skipping.", cr.Id, cr.VaultId); + continue; + } + (byte[] encPassword, byte[] pwNonce) = encryption.Encrypt(dek, cr.PlaintextPassword); + db.DockerRegistryCredentials.Add(new DockerRegistryCredential + { + Id = cr.Id, VaultId = cr.VaultId, Name = cr.Name, RegistryType = cr.RegistryType, + Server = cr.Server, Username = cr.Username, + EncryptedPassword = encPassword, PasswordNonce = pwNonce, + Email = cr.Email, AppId = cr.AppId, KubernetesClusterId = cr.KubernetesClusterId, + KubernetesSecretName = cr.KubernetesSecretName, KubernetesNamespace = cr.KubernetesNamespace, + CreatedAt = cr.CreatedAt, UpdatedAt = cr.UpdatedAt, + }); + } + if (bundle.DockerCredentials.Count > 0) + { + await db.SaveChangesAsync(); + db.ChangeTracker.Clear(); + } + + await tx.CommitAsync(); + + logger.LogInformation( + "Restore completed: {Tenants} tenants, {Apps} apps, {Secrets} secrets, {Clusters} clusters.", + bundle.Tenants.Count, bundle.Apps.Count, bundle.VaultSecrets.Count, bundle.KubernetesClusters.Count); + } + catch + { + await tx.RollbackAsync(); + throw; + } + } + + // ── Helpers ───────────────────────────────────────────────────────────── + + private static async Task InsertEntities(ApplicationDbContext db, DbSet set, List items) + where T : class + { + if (items.Count == 0) return; + // Clear navigations so EF Core only inserts the scalar/FK columns. + // Navigations are null or empty after deserialization (no Include was used + // on export), so this mainly prevents EF from trying to chase them. + foreach (T item in items) + set.Add(item); + await db.SaveChangesAsync(); + db.ChangeTracker.Clear(); + } + + private async Task InsertBatch(ApplicationDbContext db, IEnumerable items) + where T : class + { + List list = items.ToList(); + if (list.Count == 0) return; + db.Set().AddRange(list); + await db.SaveChangesAsync(); + db.ChangeTracker.Clear(); + } +} diff --git a/src/EntKube.Web/Services/CnpgService.cs b/src/EntKube.Web/Services/CnpgService.cs index 50d8e28..e19f34d 100644 --- a/src/EntKube.Web/Services/CnpgService.cs +++ b/src/EntKube.Web/Services/CnpgService.cs @@ -38,6 +38,8 @@ public class CnpgService( Guid? storageLinkId, string? backupSchedule, int retentionDays = 30, + int maxBackups = 20, + string postgresVersion = "18", CancellationToken ct = default) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -79,12 +81,13 @@ public class CnpgService( KubernetesClusterId = kubernetesClusterId, Name = name, Namespace = ns, - PostgresVersion = "18", + PostgresVersion = string.IsNullOrWhiteSpace(postgresVersion) ? "18" : postgresVersion.Trim(), Instances = instances, StorageSize = storageSize, StorageLinkId = storageLinkId, BackupSchedule = backupSchedule, RetentionDays = retentionDays, + MaxBackups = maxBackups, Status = CnpgClusterStatus.Creating }; @@ -215,6 +218,206 @@ public class CnpgService( await db.SaveChangesAsync(ct); } + /// + /// Registers an existing CNPG cluster (already running in Kubernetes) with EntKube. + /// Reads the live Cluster CRD to discover version, instances, and storage size when + /// not provided, then creates a database record with IsExternal=true and Status=Running. + /// No Kubernetes resources are created or modified. + /// + public async Task ImportClusterAsync( + Guid tenantId, + Guid kubernetesClusterId, + string name, + string ns, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster k8sCluster = await db.KubernetesClusters + .FirstAsync(k => k.Id == kubernetesClusterId, ct); + + // Query the live Cluster CRD to discover its configuration. + string postgresVersion = "17"; + int instances = 1; + string storageSize = "10Gi"; + + try + { + string clusterJson = await k8sFactory.GetJsonAsync( + $"cluster.postgresql.cnpg.io/{name}", ns, k8sCluster.Kubeconfig!, ct: ct); + + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(clusterJson); + System.Text.Json.JsonElement root = doc.RootElement; + + if (root.TryGetProperty("spec", out System.Text.Json.JsonElement spec)) + { + // imageName is like "ghcr.io/cloudnative-pg/postgresql:17.2" + if (spec.TryGetProperty("imageName", out System.Text.Json.JsonElement imgEl)) + { + string img = imgEl.GetString() ?? ""; + int colon = img.LastIndexOf(':'); + if (colon >= 0) + { + string tag = img[(colon + 1)..]; + int dot = tag.IndexOf('.'); + postgresVersion = dot > 0 ? tag[..dot] : tag; + } + } + + if (spec.TryGetProperty("instances", out System.Text.Json.JsonElement instEl)) + instances = instEl.GetInt32(); + + if (spec.TryGetProperty("storage", out System.Text.Json.JsonElement storEl) + && storEl.TryGetProperty("size", out System.Text.Json.JsonElement sizeEl)) + storageSize = sizeEl.GetString() ?? storageSize; + } + } + catch + { + // If the CRD query fails the cluster may not exist or the kubeconfig lacks + // permission — surface a clear error rather than silently using defaults. + throw new InvalidOperationException( + $"Could not read CNPG Cluster '{name}' in namespace '{ns}'. " + + "Ensure the cluster exists and the kubeconfig has read access to clusters.postgresql.cnpg.io."); + } + + CnpgCluster cnpgCluster = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KubernetesClusterId = kubernetesClusterId, + Name = name, + Namespace = ns, + PostgresVersion = postgresVersion, + Instances = instances, + StorageSize = storageSize, + IsExternal = true, + Status = CnpgClusterStatus.Running + }; + + db.CnpgClusters.Add(cnpgCluster); + await db.SaveChangesAsync(ct); + + // Pull the superuser secret into vault so credentials are immediately available. + try + { + await FetchSuperuserCredentialsAsync(tenantId, cnpgCluster.Id, ct); + } + catch + { + // Non-fatal: the cluster is registered even if the superuser secret is missing + // (e.g. kubeconfig lacks Secret read access). The user can fetch it manually. + } + + return cnpgCluster; + } + + /// + /// Removes an externally registered CNPG cluster from EntKube without touching + /// any Kubernetes resources. Also removes database records and vault secrets for + /// all databases in the cluster, but does NOT drop databases or roles in PostgreSQL. + /// + public async Task UnregisterClusterAsync(Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.Databases) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + foreach (CnpgDatabase database in cnpg.Databases) + { + await DeleteDatabaseSecretsAsync(tenantId, database.Id, ct); + } + + db.CnpgClusters.Remove(cnpg); + await db.SaveChangesAsync(ct); + } + + // ──────── Import existing databases ──────── + + /// + /// Queries the live CNPG cluster primary pod for non-system databases and returns + /// each database name with its owner role. Uses psql -t -A so output is one + /// "name|owner" entry per line with no headers. + /// + public async Task> DiscoverDatabasesAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + const string sql = """ + SELECT datname || '|' || pg_catalog.pg_get_userbyid(datdba) + FROM pg_catalog.pg_database + WHERE datistemplate = false + AND datname NOT IN ('postgres', 'template0', 'template1') + ORDER BY datname; + """; + + string output = await k8sFactory.ExecuteSqlInCnpgDatabaseWithOutputAsync( + cnpg.Name, cnpg.Namespace, "postgres", sql, cnpg.KubernetesCluster.Kubeconfig!, ct); + + List<(string Name, string Owner)> result = []; + + foreach (string line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries)) + { + string trimmed = line.Trim(); + int pipe = trimmed.IndexOf('|'); + if (pipe > 0) + result.Add((trimmed[..pipe], trimmed[(pipe + 1)..])); + } + + return result; + } + + /// + /// Registers an existing PostgreSQL database (already present in the CNPG cluster) + /// with EntKube. Creates a CnpgDatabase record with Status=Ready and stores the + /// provided credentials in vault without running any CREATE DATABASE/ROLE SQL. + /// + public async Task ImportDatabaseAsync( + Guid tenantId, Guid cnpgClusterId, + string databaseName, string ownerRole, string password, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + CnpgDatabase database = new() + { + Id = Guid.NewGuid(), + CnpgClusterId = cnpg.Id, + Name = databaseName, + Owner = ownerRole, + Status = CnpgDatabaseStatus.Ready + }; + + db.CnpgDatabases.Add(database); + await db.SaveChangesAsync(ct); + + string k8sSecretName = $"{cnpg.Name}-{databaseName}-credentials"; + string host = $"{cnpg.Name}-rw.{cnpg.Namespace}.svc.cluster.local"; + + await vaultService.InitializeVaultAsync(tenantId, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "HOST", host, k8sSecretName, cnpg.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PORT", "5432", k8sSecretName, cnpg.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "DATABASE", databaseName, k8sSecretName, cnpg.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "USERNAME", ownerRole, k8sSecretName, cnpg.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PASSWORD", password, k8sSecretName, cnpg.Namespace, ct); + + return database; + } + /// /// Upgrades a CNPG cluster to a new PostgreSQL major version. /// Updates the imageName in the Cluster CRD — CNPG handles the rolling upgrade. @@ -385,6 +588,119 @@ public class CnpgService( // ──────── Backup & Restore ──────── + /// + /// Updates the backup schedule, retention window, and max-backup count for an existing + /// cluster. Saves all three values to the DB then re-applies the ObjectStore (retention) + /// and ScheduledBackup (schedule) K8s resources so they take effect immediately. + /// Pass null for schedule to leave it unchanged; pass an empty string to remove it. + /// + public async Task UpdateBackupSettingsAsync( + Guid tenantId, Guid cnpgClusterId, + string? schedule, int retentionDays, int maxBackups, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .Include(c => c.StorageLink) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + if (schedule is not null) + cnpg.BackupSchedule = string.IsNullOrWhiteSpace(schedule) ? null : schedule; + + cnpg.RetentionDays = retentionDays; + cnpg.MaxBackups = maxBackups; + await db.SaveChangesAsync(ct); + + if (cnpg.StorageLink is null) + return; + + string kubeconfig = cnpg.KubernetesCluster.Kubeconfig!; + string s3SecretName = $"{cnpg.Name}-s3-credentials"; + + // Re-apply ObjectStore so the updated retentionPolicy reaches Barman. + await k8sFactory.ApplyManifestAsync( + BuildObjectStoreManifest(cnpg.Name, cnpg.Namespace, cnpg.StorageLink, s3SecretName, cnpg.RetentionDays), + kubeconfig, ct); + + if (!string.IsNullOrWhiteSpace(cnpg.BackupSchedule)) + { + await k8sFactory.ApplyManifestAsync( + BuildScheduledBackupManifest(cnpg.Name, cnpg.Namespace, cnpg.BackupSchedule), + kubeconfig, ct); + } + else + { + // Schedule was removed — delete the ScheduledBackup CR if it exists. + try + { + await k8sFactory.DeleteManifestAsync( + "scheduledbackups.postgresql.cnpg.io", $"{cnpg.Name}-scheduled", + cnpg.Namespace, kubeconfig, ct); + } + catch { /* CR may not exist — non-fatal */ } + } + } + + /// + /// Deletes completed Backup CRs (and their DB records) that exceed the cluster's + /// MaxBackups limit. The oldest completed backups are removed first. Running and + /// failed backups are never deleted. Best-effort — individual failures are logged + /// to the returned message list rather than thrown. + /// + public async Task CleanupOldBackupsAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + // Fetch the live backup list from K8s so we operate on current state. + List liveBackups = await FetchBackupsFromK8sAsync(cnpg, ct); + + int limit = cnpg.MaxBackups > 0 ? cnpg.MaxBackups : 20; + + List completed = liveBackups + .Where(b => b.Status == CnpgBackupStatus.Completed) + .OrderByDescending(b => b.StartedAt) + .ToList(); + + if (completed.Count <= limit) + return 0; + + List toDelete = completed.Skip(limit).ToList(); + int deleted = 0; + + foreach (CnpgBackup backup in toDelete) + { + try + { + await k8sFactory.DeleteManifestAsync( + "backups.postgresql.cnpg.io", backup.Name, + cnpg.Namespace, cnpg.KubernetesCluster.Kubeconfig!, ct); + + // Also remove from DB if present. + CnpgBackup? dbRow = await db.CnpgBackups + .FirstOrDefaultAsync(b => b.CnpgClusterId == cnpgClusterId && b.Name == backup.Name, ct); + if (dbRow is not null) + db.CnpgBackups.Remove(dbRow); + + deleted++; + } + catch { /* Non-fatal — K8s delete may fail if already gone */ } + } + + if (deleted > 0) + await db.SaveChangesAsync(ct); + + return deleted; + } + /// /// Applies (or re-applies) the ScheduledBackup CR for an existing cluster. /// If is provided it is saved to the cluster record @@ -934,6 +1250,189 @@ public class CnpgService( await SyncDatabaseCredentialsToK8sAsync(tenantId, cnpgClusterId, databaseId, ct); } + // ──────── Superuser credentials ──────── + + /// + /// Reads the CNPG-managed <cluster>-superuser Kubernetes Secret and stores + /// the postgres username and password in vault scoped to the cluster (not a database). + /// Safe to call repeatedly — upserts the vault entries. + /// + public async Task FetchSuperuserCredentialsAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + string secretName = $"{cnpg.Name}-superuser"; + string kubeconfig = cnpg.KubernetesCluster.Kubeconfig!; + + string? username = await k8sFactory.GetSecretValueAsync(secretName, "username", cnpg.Namespace, kubeconfig, ct); + string? password = await k8sFactory.GetSecretValueAsync(secretName, "password", cnpg.Namespace, kubeconfig, ct); + + if (string.IsNullOrWhiteSpace(password)) + { + throw new InvalidOperationException( + $"Could not read the CNPG superuser secret '{secretName}' in namespace '{cnpg.Namespace}'. " + + "Ensure the cluster is running and the kubeconfig has read access to Secrets."); + } + + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetCnpgClusterSecretAsync(tenantId, cnpgClusterId, "SUPERUSER_USERNAME", username ?? "postgres", ct); + await vaultService.SetCnpgClusterSecretAsync(tenantId, cnpgClusterId, "SUPERUSER_PASSWORD", password, ct); + } + + public async Task<(string? Username, string? Password)> GetSuperuserCredentialsAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + string? username = await vaultService.GetCnpgClusterSecretValueAsync(tenantId, cnpgClusterId, "SUPERUSER_USERNAME", ct); + string? password = await vaultService.GetCnpgClusterSecretValueAsync(tenantId, cnpgClusterId, "SUPERUSER_PASSWORD", ct); + return (username, password); + } + + /// + /// Returns true when all tables in the public schema are already owned by the + /// database's configured owner — i.e. no ownership fix is needed. + /// + public async Task IsDatabaseOwnershipCorrectAsync( + Guid tenantId, Guid cnpgClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + CnpgDatabase database = await db.CnpgDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.CnpgClusterId == cnpgClusterId, ct) + ?? throw new InvalidOperationException("Database not found."); + + string sql = $""" + SELECT CASE WHEN COUNT(*) = 0 THEN 'ok' ELSE 'fix' END + FROM pg_tables + WHERE schemaname = 'public' + AND tableowner != '{database.Owner}'; + """; + + string result = await k8sFactory.ExecuteSqlInCnpgDatabaseWithOutputAsync( + cnpg.Name, cnpg.Namespace, database.Name, sql, + cnpg.KubernetesCluster.Kubeconfig!, ct); + + return result.Trim() == "ok"; + } + + /// + /// Grants full ownership of all objects in the public schema to the database owner. + /// Runs as the postgres superuser via kubectl exec (peer auth — + /// no password needed). Use this to fix permission errors after restoring a + /// database dump from another Keycloak or application instance. + /// + public async Task GrantDatabaseOwnerPermissionsAsync( + Guid tenantId, Guid cnpgClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + CnpgDatabase database = await db.CnpgDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.CnpgClusterId == cnpgClusterId, ct) + ?? throw new InvalidOperationException("Database not found."); + + // Grant all schema-level permissions, reassign default privileges, and transfer + // ownership of existing objects so Liquibase DDL operations succeed. + // Running as 'postgres' superuser via kubectl exec peer auth — no password needed. + string sql = $""" + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{database.Owner}"; + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "{database.Owner}"; + GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO "{database.Owner}"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "{database.Owner}"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "{database.Owner}"; + DO $$ + DECLARE r RECORD; + BEGIN + FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' LOOP + EXECUTE format('ALTER TABLE public.%I OWNER TO "{database.Owner}"', r.tablename); + END LOOP; + FOR r IN SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' LOOP + EXECUTE format('ALTER SEQUENCE public.%I OWNER TO "{database.Owner}"', r.sequence_name); + END LOOP; + FOR r IN SELECT table_name FROM information_schema.views WHERE table_schema = 'public' LOOP + EXECUTE format('ALTER VIEW public.%I OWNER TO "{database.Owner}"', r.table_name); + END LOOP; + END $$; + """; + + await k8sFactory.ExecuteSqlInCnpgDatabaseAsync( + cnpg.Name, cnpg.Namespace, database.Name, sql, + cnpg.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task ReleaseLiquibaseLockAsync( + Guid tenantId, Guid cnpgClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + CnpgDatabase database = await db.CnpgDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.CnpgClusterId == cnpgClusterId, ct) + ?? throw new InvalidOperationException("Database not found."); + + const string sql = """ + UPDATE databasechangeloglock + SET locked = false, lockgranted = null, lockedby = null + WHERE id = 1; + """; + + await k8sFactory.ExecuteSqlInCnpgDatabaseAsync( + cnpg.Name, cnpg.Namespace, database.Name, sql, + cnpg.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task FixKeycloakRealmFrontendUrlAsync( + Guid tenantId, Guid cnpgClusterId, Guid databaseId, string frontendUrl, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgCluster cnpg = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == cnpgClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG cluster not found."); + + CnpgDatabase database = await db.CnpgDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.CnpgClusterId == cnpgClusterId, ct) + ?? throw new InvalidOperationException("Database not found."); + + // Replace frontendUrl for every realm (delete+insert avoids relying on constraint names). + // Also reset the rootUrl of built-in clients to Keycloak's ${authBaseUrl} placeholder so + // the admin/account consoles derive their base URL from the running server rather than the + // old hostname that was baked in by the restored database. + string sql = $$""" + DELETE FROM realm_attribute WHERE name = 'frontendUrl'; + INSERT INTO realm_attribute (realm_id, name, value) + SELECT id, 'frontendUrl', '{{frontendUrl}}' FROM realm; + + UPDATE keycloak_client + SET root_url = '${authBaseUrl}' + WHERE client_id IN ('security-admin-console', 'account', 'account-console', 'broker'); + """; + + await k8sFactory.ExecuteSqlInCnpgDatabaseAsync( + cnpg.Name, cnpg.Namespace, database.Name, sql, + cnpg.KubernetesCluster.Kubeconfig!, ct); + } + // ──────── DatabaseBinding management ──────── /// @@ -1490,6 +1989,7 @@ public class CnpgService( sb.AppendLine("spec:"); sb.AppendLine($" instances: {cnpg.Instances}"); sb.AppendLine($" imageName: ghcr.io/cloudnative-pg/postgresql:{cnpg.PostgresVersion}"); + sb.AppendLine(" enableSuperuserAccess: true"); // Rolling update strategy: replicas are updated first, then a switchover // promotes a replica to primary before updating the old primary. This gives diff --git a/src/EntKube.Web/Services/ComponentCatalog.cs b/src/EntKube.Web/Services/ComponentCatalog.cs index 6c77aca..ea7592d 100644 --- a/src/EntKube.Web/Services/ComponentCatalog.cs +++ b/src/EntKube.Web/Services/ComponentCatalog.cs @@ -638,6 +638,106 @@ public static class ComponentCatalog """ }, + new CatalogEntry + { + Key = "loki", + DisplayName = "Grafana Loki", + Description = "Log aggregation system. Collects, indexes, and queries logs from all pods on the cluster. Integrates with Grafana for log browsing and with the EntKube log viewer.", + Icon = "bi-journal-text", + Category = "Monitoring", + HelmRepoUrl = "https://grafana.github.io/helm-charts", + HelmChartName = "loki", + HelmChartVersion = "6.30.0", + DefaultNamespace = "monitoring", + DefaultReleaseName = "loki", + FormFields = + [ + new ComponentFormField + { + Key = "deployment-mode", Label = "Deployment Mode", + YamlPath = "deploymentMode", Type = FormFieldType.Select, + DefaultValue = "SingleBinary", + Options = ["SingleBinary", "SimpleScalable"], + HelpText = "SingleBinary runs all Loki components in one pod — suitable for most clusters. SimpleScalable splits read/write/backend for higher throughput." + }, + new ComponentFormField + { + Key = "storage-link", Label = "S3 Chunk Storage", + YamlPath = "loki:storage-link-id", Type = FormFieldType.StorageLink, + HelpText = "S3-compatible bucket for log chunk storage. Leave empty to use local filesystem (not recommended for production)." + }, + new ComponentFormField + { + Key = "storage-class", Label = "Storage Class", + YamlPath = "singleBinary.persistence.storageClass", Type = FormFieldType.Text, + Placeholder = "e.g. local-path, longhorn, standard", + HelpText = "StorageClass for the data volume. Run 'kubectl get storageclass' to find the name. Use '-' to disable dynamic provisioning." + }, + new ComponentFormField + { + Key = "cpu-request", Label = "CPU Request", + YamlPath = "singleBinary.resources.requests.cpu", Type = FormFieldType.Text, + DefaultValue = "100m", Placeholder = "e.g. 100m, 250m" + }, + new ComponentFormField + { + Key = "memory-request", Label = "Memory Request", + YamlPath = "singleBinary.resources.requests.memory", Type = FormFieldType.Text, + DefaultValue = "256Mi", Placeholder = "e.g. 256Mi, 512Mi" + }, + // Hidden secret fields — written by LokiService.WriteStorageHelmValuesAsync, + // injected at install time via InjectSecretsIntoValuesAsync. + new ComponentFormField + { + Key = "loki-s3-access-key", Label = "S3 Access Key", + YamlPath = "loki.storage.s3.accessKeyId", Type = FormFieldType.Password, + StoreAsSecret = true, SecretName = "loki-s3-access-key", Hidden = true + }, + new ComponentFormField + { + Key = "loki-s3-secret-key", Label = "S3 Secret Key", + YamlPath = "loki.storage.s3.secretAccessKey", Type = FormFieldType.Password, + StoreAsSecret = true, SecretName = "loki-s3-secret-key", Hidden = true + } + ], + DefaultValues = """ + deploymentMode: SingleBinary + + loki: + auth_enabled: false + commonConfig: + replication_factor: 1 + storage: + type: filesystem + useTestSchema: true + + singleBinary: + replicas: 1 + persistence: + enabled: true + size: 10Gi + resources: + requests: + cpu: 100m + memory: 256Mi + + # Disable distributed components for SingleBinary mode + read: + replicas: 0 + write: + replicas: 0 + backend: + replicas: 0 + + # The chart enables Memcached caches by default with 8 GiB requests each, + # which is excessive for most clusters. Disable them for SingleBinary. + chunksCache: + enabled: false + resultsCache: + enabled: false + """ + }, + // ── Storage ── new CatalogEntry @@ -714,6 +814,41 @@ public static class ComponentCatalog """ }, + new CatalogEntry + { + Key = "minio-operator", + DisplayName = "MinIO Operator", + Description = "Kubernetes operator for managing MinIO Tenant instances. Installs the minio.min.io/v2 Tenant CRD so tenants can be provisioned as first-class Kubernetes resources.", + Icon = "bi-bucket-fill", + Category = "Storage", + HelmRepoUrl = "https://operator.min.io/", + HelmChartName = "operator", + DefaultNamespace = "minio-operator", + DefaultReleaseName = "minio-operator", + FormFields = + [ + new ComponentFormField + { + Key = "cpu-request", Label = "CPU Request", + YamlPath = "operator.resources.requests.cpu", Type = FormFieldType.Text, + DefaultValue = "200m", Placeholder = "e.g. 200m, 500m" + }, + new ComponentFormField + { + Key = "memory-request", Label = "Memory Request", + YamlPath = "operator.resources.requests.memory", Type = FormFieldType.Text, + DefaultValue = "256Mi", Placeholder = "e.g. 256Mi, 512Mi" + } + ], + DefaultValues = """ + operator: + resources: + requests: + memory: 256Mi + cpu: 200m + """ + }, + // ── Databases ── new CatalogEntry @@ -950,6 +1085,663 @@ public static class ComponentCatalog memory: 512Mi cpu: 250m """ + }, + + // ── Registry ── + + new CatalogEntry + { + Key = "harbor", + DisplayName = "Harbor", + Description = "Open-source container registry with role-based access control, image signing, vulnerability scanning, and S3-compatible artifact storage. Requires a CNPG database and optionally an S3 bucket for production use.", + Icon = "bi-box-seam", + Category = "Registry", + HelmRepoUrl = "https://helm.goharbor.io", + HelmChartName = "harbor", + HelmChartVersion = "1.16.1", + DefaultNamespace = "harbor", + DefaultReleaseName = "harbor", + Dependencies = ["cert-manager", "letsencrypt-issuer", "cloudnative-pg"], + FormFields = + [ + new ComponentFormField + { + Key = "cnpg-database", Label = "Database", + YamlPath = "harbor:database-id", Type = FormFieldType.CnpgDatabase, + HelpText = "Managed PostgreSQL database (credentials injected as Helm values at install time)" + }, + new ComponentFormField + { + Key = "storage-link", Label = "S3 Storage", + YamlPath = "harbor:storage-link-id", Type = FormFieldType.StorageLink, + HelpText = "S3-compatible bucket for registry artifact storage (optional — uses PVC when not set)" + }, + new ComponentFormField + { + Key = "admin-password", Label = "Admin Password", + YamlPath = "harborAdminPassword", Type = FormFieldType.Password, + Placeholder = "Strong password for the Harbor admin account", + HelpText = "Stored in vault and injected at install time", + StoreAsSecret = true, + SecretName = "HARBOR_ADMIN_PASSWORD" + }, + new ComponentFormField + { + Key = "hostname", Label = "Public Hostname", + YamlPath = "harbor:hostname", Type = FormFieldType.Text, + Placeholder = "registry.example.com", + HelpText = "Creates an HTTPRoute and Certificate for external access" + }, + new ComponentFormField + { + Key = "tls-mode", Label = "TLS Mode", + YamlPath = "harbor:tls-mode", Type = FormFieldType.Select, + DefaultValue = "ClusterIssuer", + Options = ["ClusterIssuer", "Manual"] + }, + new ComponentFormField + { + Key = "cluster-issuer", Label = "Cluster Issuer", + YamlPath = "harbor:cluster-issuer", Type = FormFieldType.ClusterIssuer, + DefaultValue = "letsencrypt-prod", + HelpText = "cert-manager ClusterIssuer to issue the TLS certificate", + DependsOnKey = "tls-mode", DependsOnValue = "ClusterIssuer" + }, + new ComponentFormField + { + Key = "tls-cert", Label = "TLS Certificate (PEM)", + YamlPath = "harbor:tls-cert", Type = FormFieldType.Password, + Placeholder = "-----BEGIN CERTIFICATE-----", + HelpText = "Full certificate chain in PEM format", + DependsOnKey = "tls-mode", DependsOnValue = "Manual" + }, + new ComponentFormField + { + Key = "tls-key", Label = "TLS Private Key (PEM)", + YamlPath = "harbor:tls-key", Type = FormFieldType.Password, + Placeholder = "-----BEGIN PRIVATE KEY-----", + HelpText = "Private key in PEM format", + DependsOnKey = "tls-mode", DependsOnValue = "Manual" + }, + new ComponentFormField + { + Key = "cpu-request", Label = "CPU Request", + YamlPath = "core.resources.requests.cpu", Type = FormFieldType.Text, + DefaultValue = "100m", Placeholder = "e.g. 100m, 250m" + }, + new ComponentFormField + { + Key = "memory-request", Label = "Memory Request", + YamlPath = "core.resources.requests.memory", Type = FormFieldType.Text, + DefaultValue = "256Mi", Placeholder = "e.g. 256Mi, 512Mi" + }, + // Hidden secret-backed fields — injected programmatically by HarborService, + // not entered by the user. InjectSecretsIntoValuesAsync picks these up at install time. + new ComponentFormField + { + Key = "harbor-db-password", Label = "DB Password", + YamlPath = "database.external.password", Type = FormFieldType.Password, + StoreAsSecret = true, SecretName = "harbor-db-password", Hidden = true + }, + new ComponentFormField + { + Key = "harbor-s3-access-key", Label = "S3 Access Key", + YamlPath = "persistence.imageChartStorage.s3.accesskey", Type = FormFieldType.Password, + StoreAsSecret = true, SecretName = "harbor-s3-access-key", Hidden = true + }, + new ComponentFormField + { + Key = "harbor-s3-secret-key", Label = "S3 Secret Key", + YamlPath = "persistence.imageChartStorage.s3.secretkey", Type = FormFieldType.Password, + StoreAsSecret = true, SecretName = "harbor-s3-secret-key", Hidden = true + } + ], + DefaultValues = """ + expose: + type: clusterIP + tls: + enabled: false + + # externalURL and expose.clusterIP.name are written by EntKube when a hostname + # is configured, so they always match the release name used for the route backend. + externalURL: https://registry.example.com + + # Use internal Postgres by default; overridden to 'external' by HarborService + # when a CNPG database is configured. + database: + type: internal + + persistence: + enabled: true + resourcePolicy: "keep" + # Storage type defaults to filesystem; overridden to 's3' by HarborService + # when a StorageLink is configured. + imageChartStorage: + type: filesystem + disableredirect: false + + core: + resources: + requests: + cpu: 100m + memory: 256Mi + """ + }, + + // ── Messaging ── + + new CatalogEntry + { + Key = "rabbitmq-cluster-operator", + DisplayName = "RabbitMQ Cluster Operator", + Description = "Kubernetes operator that manages RabbitMQ broker clusters. Provisions RabbitmqCluster CRDs as StatefulSets with automatic secret generation, persistent storage, and cluster health conditions. Installed via the official GitHub release manifest.", + Icon = "bi-collection", + Category = "Messaging", + ComponentType = "ManifestUrl", + HelmRepoUrl = "https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml", + HelmChartName = "", + DefaultNamespace = "rabbitmq-system", + DefaultReleaseName = "rabbitmq-cluster-operator", + FormFields = [], + DefaultValues = "" + }, + + new CatalogEntry + { + Key = "rabbitmq-messaging-topology-operator", + DisplayName = "RabbitMQ Messaging Topology Operator", + Description = "Manages RabbitMQ topology objects (vhosts, queues, exchanges, bindings, policies, users, permissions) as Kubernetes CRDs. Requires cert-manager for webhook TLS certificate management.", + Icon = "bi-diagram-3", + Category = "Messaging", + ComponentType = "ManifestUrl", + HelmRepoUrl = "https://github.com/rabbitmq/messaging-topology-operator/releases/latest/download/messaging-topology-operator-with-certmanager.yaml", + HelmChartName = "", + DefaultNamespace = "rabbitmq-system", + DefaultReleaseName = "rabbitmq-topology-operator", + Dependencies = ["rabbitmq-cluster-operator", "cert-manager"], + FormFields = [], + DefaultValues = "" + }, + + // ── Cache ── + + new CatalogEntry + { + Key = "redis-operator", + DisplayName = "Redis Operator (OT-Container-Kit)", + Description = "Kubernetes operator for Redis clusters by OT-Container-Kit. Manages RedisCluster CRDs (redis.redis.opstreelabs.in/v1beta2) as sharded StatefulSets with persistence, Prometheus metrics via a sidecar exporter, and built-in auth via pre-provisioned Secrets.", + Icon = "bi-lightning-charge", + Category = "Cache", + ComponentType = "HelmChart", + HelmRepoUrl = "https://ot-container-kit.github.io/helm-charts/", + HelmChartName = "redis-operator", + DefaultNamespace = "redis-operator", + DefaultReleaseName = "redis-operator", + FormFields = [], + DefaultValues = "" + }, + + // ── Networking ── + + new CatalogEntry + { + Key = "strongswan-gateway", + DisplayName = "StrongSwan IPsec Gateway", + Description = "IKEv2/IPsec gateway pod for site-to-site VPN tunnels. Exposes UDP 500/4500 via a LoadBalancer Service. swanctl.conf and the PSK secret are synced from EntKube — use the VPN tunnel's 'Sync Config' action after install.", + Icon = "bi-shield-lock", + Category = "Networking", + ComponentType = "Manifest", + // No Helm repo — this is a raw manifest applied via kubectl apply. + HelmRepoUrl = "", + HelmChartName = "", + DefaultNamespace = "vpn-system", + DefaultReleaseName = "strongswan", + // The DefaultValues field holds the initial Kubernetes manifest YAML. + // Operators can edit this YAML in the component's advanced editor before install. + // After install, use VPN > Sync Config to push the swanctl.conf + PSK. + DefaultValues = """ + # StrongSwan IKEv2/IPsec gateway + # Deployed as a Deployment (not DaemonSet) so it gets exactly one pod + # and the LoadBalancer Service routes to it. + # The swanctl.conf and PSK are managed by EntKube via the VPN tunnel's + # "Sync Config" action, which writes a ConfigMap + Secret into this namespace. + --- + apiVersion: v1 + kind: Namespace + metadata: + name: vpn-system + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: strongswan + namespace: vpn-system + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: strongswan + namespace: vpn-system + labels: + app: strongswan + spec: + replicas: 1 + selector: + matchLabels: + app: strongswan + template: + metadata: + labels: + app: strongswan + spec: + serviceAccountName: strongswan + containers: + - name: strongswan + # linux/amd64 only — change tag to match your cluster arch if needed. + # Available tags: 6.0.6, 6.0.5, latest (see hub.docker.com/r/strongx509/strongswan) + image: strongx509/strongswan:6.0.6 + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + - NET_RAW + - SYS_MODULE + ports: + - name: ike + containerPort: 500 + protocol: UDP + - name: nat-t + containerPort: 4500 + protocol: UDP + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + memory: 128Mi + volumeMounts: + # Both connections and secrets must be in conf.d/ so swanctl --load-all + # picks them up. Use subPath mounts to avoid clobbering each other. + - name: config + mountPath: /etc/swanctl/conf.d/connections.conf + subPath: connections.conf + readOnly: true + - name: secrets + mountPath: /etc/swanctl/conf.d/secrets.conf + subPath: secrets.conf + readOnly: true + volumes: + - name: config + configMap: + name: strongswan-config + optional: true + - name: secrets + secret: + secretName: strongswan-secrets + optional: true + --- + apiVersion: v1 + kind: Service + metadata: + name: strongswan + namespace: vpn-system + annotations: + # Remove this annotation if your cloud provider does not support it + # service.beta.kubernetes.io/aws-load-balancer-type: nlb + spec: + type: LoadBalancer + selector: + app: strongswan + ports: + - name: ike + port: 500 + targetPort: 500 + protocol: UDP + - name: nat-t + port: 4500 + targetPort: 4500 + protocol: UDP + """, + FormFields = [] + }, + + new CatalogEntry + { + Key = "wg-easy", + DisplayName = "WireGuard Easy (wg-easy)", + Description = "WireGuard VPN server with web UI. Routes WireGuard UDP 51820 through the existing Istio external gateway via an Envoy UDP proxy (EnvoyFilter), and exposes the web UI as an HTTPRoute on the same gateway. A dedicated LoadBalancer Service shares the Istio gateway's existing external IP (MetalLB: allow-shared-ip; cloud providers: set loadBalancerIP). All resources are lifecycle-managed — uninstall removes everything including the LB rule.", + Icon = "bi-shield-check", + Category = "Networking", + ComponentType = "Manifest", + HelmRepoUrl = "", + HelmChartName = "", + DefaultNamespace = "wg-easy", + DefaultReleaseName = "wg-easy", + DefaultValues = """ + --- + apiVersion: v1 + kind: Namespace + metadata: + name: wg-easy + --- + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: wg-easy-config + namespace: wg-easy + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + --- + # Internal ClusterIP — target for both the Envoy UDP proxy and the HTTPRoute web UI. + apiVersion: v1 + kind: Service + metadata: + name: wg-easy-svc + namespace: wg-easy + spec: + type: ClusterIP + selector: + app: wg-easy + ports: + - name: wireguard + port: 51820 + targetPort: 51820 + protocol: UDP + - name: web-ui + port: 51821 + targetPort: 51821 + protocol: TCP + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: wg-easy + namespace: wg-easy + labels: + app: wg-easy + spec: + replicas: 1 + selector: + matchLabels: + app: wg-easy + strategy: + type: Recreate + template: + metadata: + labels: + app: wg-easy + spec: + containers: + - name: wg-easy + image: ghcr.io/wg-easy/wg-easy:14 + imagePullPolicy: IfNotPresent + securityContext: + privileged: true + ports: + - name: wireguard + containerPort: 51820 + protocol: UDP + - name: web-ui + containerPort: 51821 + protocol: TCP + # WG_HOST and PASSWORD_HASH come from the wg-easy-env K8s Secret + # (created by EntKube vault sync — set the FormFields before installing). + envFrom: + - secretRef: + name: wg-easy-env + env: + - name: LANG + value: en + - name: WG_PRE_UP + value: "sysctl -w net.ipv4.ip_forward=1" + - name: WG_PORT + value: "51820" + - name: WG_DEFAULT_ADDRESS + value: "10.8.0.x" + - name: WG_DEFAULT_DNS + value: "10.96.0.10" + - name: WG_ALLOWED_IPS + value: "10.96.0.0/12,10.244.0.0/16" + - name: WG_MTU + value: "1480" + volumeMounts: + - name: config + mountPath: /etc/wireguard + - name: modules + mountPath: /lib/modules + readOnly: true + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + memory: 256Mi + volumes: + - name: config + persistentVolumeClaim: + claimName: wg-easy-config + - name: modules + hostPath: + path: /lib/modules + restartPolicy: Always + terminationGracePeriodSeconds: 30 + --- + # EnvoyFilter: adds a UDP proxy listener on 51820 to the Istio gateway pods. + # %%WG_GATEWAY%% is substituted at apply-time with the release name of the + # gateway chosen in the component's FormFields (e.g. istio-ingress-external). + apiVersion: networking.istio.io/v1alpha3 + kind: EnvoyFilter + metadata: + name: wg-easy-udp-proxy + namespace: istio-system + spec: + workloadSelector: + labels: + app: %%WG_GATEWAY%% + configPatches: + - applyTo: LISTENER + match: + context: GATEWAY + patch: + operation: ADD + value: + name: udp_0.0.0.0_51820 + address: + socket_address: + protocol: UDP + address: 0.0.0.0 + port_value: 51820 + udp_listener_config: + downstream_socket_config: {} + listener_filters: + - name: envoy.filters.udp_listener.udp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + stat_prefix: wg_easy_wireguard + cluster: outbound|51820||wg-easy-svc.wg-easy.svc.cluster.local + idle_timeout: 90s + --- + # Dedicated LoadBalancer Service that exposes UDP 51820 on the SAME external IP + # as the Istio gateway, so no new LB IP is provisioned. + # + # MetalLB (on-prem): 'allow-shared-ip' tells MetalLB to assign the same IP + # as the istio-ingress-external Service. No other changes needed. + # + # Cloud providers (AKS/GKE/EKS): set spec.loadBalancerIP to the Istio + # gateway's existing external IP. Most cloud LB implementations will add + # the UDP rule to the same backend pool rather than allocating a new IP. + # Azure example annotation: service.beta.kubernetes.io/azure-pip-name: + # GKE: set loadBalancerIP below + # + # This Service is a proper manifest resource — 'kubectl delete' on uninstall + # removes it cleanly and the LB rule is released automatically. + apiVersion: v1 + kind: Service + metadata: + name: istio-ingress-wireguard + namespace: istio-system + annotations: + metallb.universe.tf/allow-shared-ip: %%WG_GATEWAY%% + # Uncomment and set for cloud providers: + # service.beta.kubernetes.io/azure-pip-name: "" + spec: + type: LoadBalancer + # loadBalancerIP: "" # set to the Istio gateway's existing external IP for cloud providers + selector: + app: %%WG_GATEWAY%% + ports: + - name: wireguard + port: 51820 + targetPort: 51820 + protocol: UDP + --- + # HTTPRoute: exposes the wg-easy web UI through the Istio external gateway. + # Update 'hostnames' to match your WG_HOST value (the gateway's external DNS name). + # Requires the Gateway to allow routes from outside its own namespace — + # standard Istio setup includes allowedRoutes.namespaces.from: All. + apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: wg-easy-ui + namespace: wg-easy + spec: + parentRefs: + - name: %%WG_GATEWAY%% + namespace: istio-system + hostnames: + - "vpn.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: wg-easy-svc + port: 51821 + """, + FormFields = + [ + new ComponentFormField + { + Key = "wg-gateway", + Label = "Istio Gateway", + YamlPath = "", + Type = FormFieldType.GatewaySelector, + StoreAsSecret = true, + SecretName = "WG_GATEWAY_NAME", + ManifestPlaceholder = "%%WG_GATEWAY%%", + HelpText = "Which Istio gateway to route WireGuard through. Selecting one auto-detects its LoadBalancer IP and fills in the hostname below." + }, + new ComponentFormField + { + Key = "wg-host", + Label = "Public Hostname / IP", + YamlPath = "", + Type = FormFieldType.Text, + StoreAsSecret = true, + SecretName = "WG_HOST", + KubernetesSecretName = "wg-easy-env", + KubernetesSecretNamespace = "wg-easy", + Placeholder = "vpn.example.com", + HelpText = "Auto-detected from the selected gateway's LoadBalancer IP. You can override this with a DNS hostname — just make sure it resolves to the same IP. Also update the HTTPRoute hostnames in the advanced YAML editor to match." + }, + new ComponentFormField + { + Key = "admin-password", + Label = "Admin Password", + YamlPath = "", + Type = FormFieldType.Password, + StoreAsSecret = true, + SecretName = "PASSWORD_HASH", + KubernetesSecretName = "wg-easy-env", + KubernetesSecretNamespace = "wg-easy", + BcryptOnSync = true, + HelpText = "Plain-text password stored in vault. EntKube automatically bcrypt-hashes it (cost 12) when syncing to the wg-easy-env K8s Secret as PASSWORD_HASH." + } + ] + }, + + new CatalogEntry + { + Key = "submariner-broker", + DisplayName = "Submariner Broker", + Description = "Coordination hub for the Submariner cluster mesh. Stores cluster and endpoint metadata so gateway pods can discover each other. Install on exactly one cluster per mesh — this cluster does not carry data-plane traffic.", + Icon = "bi-diagram-3", + Category = "Networking", + HelmRepoUrl = "https://submariner.io/charts", + HelmChartName = "submariner-k8s-broker", + HelmChartVersion = "0.17.0", + DefaultNamespace = "submariner-k8s-broker", + DefaultReleaseName = "submariner-broker", + DefaultValues = "", + FormFields = [] + }, + + new CatalogEntry + { + Key = "submariner-gateway", + DisplayName = "Submariner Gateway", + Description = "IPsec data-plane gateway for the Submariner cluster mesh. Install on every cluster that joins the mesh (including the broker host). Handles encrypted pod-to-pod and service-to-service traffic between clusters via Libreswan.", + Icon = "bi-hdd-network", + Category = "Networking", + HelmRepoUrl = "https://submariner.io/charts", + HelmChartName = "submariner-operator", + HelmChartVersion = "0.17.0", + DefaultNamespace = "submariner-operator", + DefaultReleaseName = "submariner", + DefaultValues = """ + submariner: + cableDriver: libreswan + """, + FormFields = + [ + new ComponentFormField + { + Key = "broker-api-server", + Label = "Broker API Server URL", + YamlPath = "broker.server", + Type = FormFieldType.Text, + Placeholder = "https://:6443" + }, + new ComponentFormField + { + Key = "broker-token", + Label = "Broker Token", + YamlPath = "broker.token", + Type = FormFieldType.Password, + StoreAsSecret = true + }, + new ComponentFormField + { + Key = "cluster-cidr", + Label = "Pod CIDR", + YamlPath = "submariner.clusterCidr", + Type = FormFieldType.Text, + Placeholder = "e.g. 10.244.0.0/16" + }, + new ComponentFormField + { + Key = "service-cidr", + Label = "Service CIDR", + YamlPath = "submariner.serviceCidr", + Type = FormFieldType.Text, + Placeholder = "e.g. 10.96.0.0/12" + }, + new ComponentFormField + { + Key = "cable-driver", + Label = "Cable Driver", + YamlPath = "submariner.cableDriver", + Type = FormFieldType.Select, + DefaultValue = "libreswan", + Options = ["libreswan", "wireguard"] + } + ] } ]; diff --git a/src/EntKube.Web/Services/ComponentLifecycleService.cs b/src/EntKube.Web/Services/ComponentLifecycleService.cs index 7ac8a1c..5535d3a 100644 --- a/src/EntKube.Web/Services/ComponentLifecycleService.cs +++ b/src/EntKube.Web/Services/ComponentLifecycleService.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO.Compression; using System.Text; using System.Text.Json; +using BCrypt.Net; using EntKube.Web.Data; using k8s; using k8s.Models; @@ -182,9 +183,12 @@ public class ComponentLifecycleService(IDbContextFactory d "Component has an operation in progress. Wait for it to complete."); } - // Validate minimum required fields — ManifestUrl components use a URL instead of a chart name. + // Validate minimum required fields. + // ManifestUrl uses HelmRepoUrl as the URL; Manifest uses HelmValues as raw YAML. + // Only HelmChart type requires a chart name. - if (component.ComponentType != "ManifestUrl" && string.IsNullOrWhiteSpace(component.HelmChartName)) + if (component.ComponentType != "ManifestUrl" && component.ComponentType != "Manifest" + && string.IsNullOrWhiteSpace(component.HelmChartName)) { throw new InvalidOperationException( "Helm chart name is required. Configure the component before installing."); @@ -547,16 +551,22 @@ public class ComponentLifecycleService(IDbContextFactory d // Manifest components are applied via kubectl, not helm. // Their HelmValues field contains raw Kubernetes YAML manifests. + // Substitute any %%PLACEHOLDER%% tokens from vault-backed FormFields before applying. if (component.ComponentType == "Manifest") { + string manifestYaml = await SubstituteManifestPlaceholdersAsync( + component.HelmValues ?? "", component, ct); + + // Do not pass Namespace — every resource in a Manifest already declares its own + // namespace in metadata. Passing --namespace would cause kubectl to reject any + // resource whose metadata.namespace differs from the component's default namespace. return new HelmCommand { Operation = "kubectl-apply", ReleaseName = component.ReleaseName ?? component.Name, - Namespace = component.Namespace, - HasValues = !string.IsNullOrWhiteSpace(component.HelmValues), - ValuesYaml = component.HelmValues + HasValues = !string.IsNullOrWhiteSpace(manifestYaml), + ValuesYaml = manifestYaml }; } @@ -617,11 +627,11 @@ public class ComponentLifecycleService(IDbContextFactory d if (component.ComponentType == "Manifest") { + // No Namespace — same reason as install: resources declare their own namespaces. return new HelmCommand { Operation = "kubectl-delete", ReleaseName = releaseName, - Namespace = component.Namespace, HasValues = !string.IsNullOrWhiteSpace(component.HelmValues), ValuesYaml = component.HelmValues }; @@ -652,6 +662,83 @@ public class ComponentLifecycleService(IDbContextFactory d /// then decrypts the corresponding vault secrets and injects them at the correct YAML paths. /// Returns the merged YAML (or the original if no secrets exist). /// + /// + /// Queries a Service's external LoadBalancer IP (or hostname for providers that + /// use DNS names instead of IPs, such as AKS with Azure DNS integration). + /// Returns null if the LB is not yet assigned or the cluster is unreachable. + /// + public async Task GetServiceExternalIpAsync( + Guid clusterId, string serviceName, string ns, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster cluster = await db.KubernetesClusters + .FirstOrDefaultAsync(c => c.Id == clusterId, ct) + ?? throw new InvalidOperationException("Cluster not found."); + + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return null; + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + try + { + await File.WriteAllTextAsync(tempKubeconfig, cluster.Kubeconfig, ct); + + // Try IP first — most cloud providers and MetalLB set .ingress[0].ip + HelmExecutionResult ipResult = await RunProcessAsync("kubectl", + $"get svc {serviceName} -n {ns} --kubeconfig {tempKubeconfig} -o jsonpath={{.status.loadBalancer.ingress[0].ip}}", + ct); + + string ip = ipResult.Output.Trim(); + if (!string.IsNullOrEmpty(ip)) + return ip; + + // Fallback to hostname — AWS ELB and some AKS configs use hostname + HelmExecutionResult hostnameResult = await RunProcessAsync("kubectl", + $"get svc {serviceName} -n {ns} --kubeconfig {tempKubeconfig} -o jsonpath={{.status.loadBalancer.ingress[0].hostname}}", + ct); + + string hostname = hostnameResult.Output.Trim(); + return string.IsNullOrEmpty(hostname) ? null : hostname; + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + } + } + + /// + /// Replaces %%PLACEHOLDER%% tokens in a Manifest-type component's YAML with + /// values retrieved from the vault. This allows FormFields like GatewaySelector + /// to inject values that must appear verbatim in the YAML (e.g. a gateway name + /// in an EnvoyFilter workloadSelector) rather than at a YAML dot-notation path. + /// Only fields with both StoreAsSecret=true and ManifestPlaceholder set are processed. + /// + private async Task SubstituteManifestPlaceholdersAsync( + string manifestYaml, ClusterComponent component, CancellationToken ct) + { + CatalogEntry? catalog = ComponentCatalog.GetByKey(component.Name); + if (catalog is null) + return manifestYaml; + + IEnumerable placeholderFields = catalog.FormFields + .Where(f => f.StoreAsSecret && f.ManifestPlaceholder != null); + + Guid tenantId = component.Cluster.TenantId; + + foreach (ComponentFormField field in placeholderFields) + { + string secretName = field.SecretName ?? field.Key; + string? value = await vaultService.GetComponentSecretValueAsync( + tenantId, component.Id, secretName, ct); + + if (!string.IsNullOrEmpty(value)) + manifestYaml = manifestYaml.Replace(field.ManifestPlaceholder!, value, StringComparison.Ordinal); + } + + return manifestYaml; + } + private async Task InjectSecretsIntoValuesAsync( ClusterComponent component, CancellationToken ct) { @@ -756,6 +843,14 @@ public class ComponentLifecycleService(IDbContextFactory d .Where(s => s.ComponentId == componentId && s.SyncToKubernetes) .ToListAsync(ct); + // Build a lookup of secret name → FormField so we can apply transformations + // (e.g. BcryptOnSync) before writing to the K8s Secret. + CatalogEntry? catalog = ComponentCatalog.GetByKey(component.Name); + Dictionary secretFieldsByName = catalog?.FormFields + .Where(f => f.StoreAsSecret) + .ToDictionary(f => f.SecretName ?? f.Key, f => f, StringComparer.OrdinalIgnoreCase) + ?? []; + if (allSecrets.Count == 0) { return new HelmExecutionResult @@ -805,6 +900,15 @@ public class ComponentLifecycleService(IDbContextFactory d if (plainValue is not null) { + // If the catalog field requests bcrypt transformation, hash the + // plaintext before writing it to the K8s Secret. The vault retains + // the original plaintext so it can be revealed in the UI. + if (secretFieldsByName.TryGetValue(vaultSecret.Name, out ComponentFormField? field) + && field.BcryptOnSync) + { + plainValue = BCrypt.Net.BCrypt.HashPassword(plainValue, workFactor: 12); + } + literals.Add($"--from-literal={vaultSecret.Name}={plainValue}"); } } @@ -913,6 +1017,38 @@ public class ComponentLifecycleService(IDbContextFactory d } } + /// + /// Applies or deletes a raw YAML manifest against an arbitrary cluster. + /// Used by VpnService to push StrongSwan ConfigMap and Secret. + /// + public async Task ApplyRawManifestAsync( + KubernetesCluster cluster, string manifestYaml, bool delete = false, + CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return new HelmExecutionResult { Success = false, Output = "No kubeconfig stored for this cluster." }; + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + string tempManifest = Path.Combine(Path.GetTempPath(), $"entkube-manifest-{Guid.NewGuid()}.yaml"); + + try + { + await File.WriteAllTextAsync(tempKubeconfig, cluster.Kubeconfig, ct); + await File.WriteAllTextAsync(tempManifest, manifestYaml, ct); + + string operation = delete ? "delete" : "apply"; + string args = $"{operation} -f {tempManifest} --kubeconfig {tempKubeconfig}"; + if (delete) args += " --ignore-not-found"; + + return await RunProcessAsync("kubectl", args, ct); + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + if (File.Exists(tempManifest)) File.Delete(tempManifest); + } + } + /// /// Applies or deletes Kubernetes manifests using kubectl. /// The manifest YAML is written to a temp file and applied/deleted. @@ -990,13 +1126,28 @@ public class ComponentLifecycleService(IDbContextFactory d ?? throw new InvalidOperationException("Component not found."); List allRoutes = []; + List<(string OldName, string Namespace)> orphanedRoutes = []; + foreach (ClusterComponent comp in component.Cluster.Components) { foreach (ExternalRoute r in comp.ExternalRoutes) + { r.Component = comp; + // Compute the old service-name-based route name before fixing — if it differs + // from the new hostname-based name, the old HTTPRoute is orphaned in the cluster. + string oldRouteName = r.ServiceName + "-route"; + string newRouteName = ExternalRouteService.ToListenerName(r.Hostname) + "-route"; + if (!string.Equals(oldRouteName, newRouteName, StringComparison.Ordinal)) + orphanedRoutes.Add((oldRouteName, comp.Namespace ?? "default")); + + FixRouteServiceName(r, comp); + } allRoutes.AddRange(comp.ExternalRoutes); } + // Persist any service-name corrections to the DB so subsequent applies don't re-add orphans. + await db.SaveChangesAsync(ct); + if (allRoutes.Count == 0) { return new HelmExecutionResult { Success = true, Output = "" }; @@ -1028,6 +1179,14 @@ public class ComponentLifecycleService(IDbContextFactory d { await File.WriteAllTextAsync(tempKubeconfig, component.Cluster.Kubeconfig, ct); await File.WriteAllTextAsync(tempManifest, combinedYaml, ct); + + // Delete orphaned service-name-based HTTPRoutes before applying hostname-based ones. + foreach ((string oldName, string ns) in orphanedRoutes) + { + await RunProcessAsync("kubectl", + $"delete httproute {oldName} -n {ns} --kubeconfig {tempKubeconfig} --ignore-not-found", ct); + } + return await RunProcessAsync("kubectl", $"apply -f {tempManifest} --kubeconfig {tempKubeconfig}", ct); } finally @@ -1352,6 +1511,67 @@ public class ComponentLifecycleService(IDbContextFactory d } } + /// + /// Returns the names of all cert-manager ClusterIssuer resources on the cluster. + /// Used to populate ClusterIssuer selector dropdowns in the UI. + /// Returns an empty list if kubectl fails or cert-manager is not installed. + /// + public async Task> ListClusterIssuersAsync(Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster? cluster = await db.KubernetesClusters + .FirstOrDefaultAsync(c => c.Id == clusterId, ct); + + if (cluster is null || string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return []; + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + + try + { + await File.WriteAllTextAsync(tempKubeconfig, cluster.Kubeconfig, ct); + + HelmExecutionResult result = await RunProcessAsync( + "kubectl", + $"get clusterissuers.cert-manager.io -o json --kubeconfig {tempKubeconfig}", + ct); + + if (!result.Success || string.IsNullOrWhiteSpace(result.Output)) + return []; + + return ParseJsonResourceNames(result.Output); + } + catch + { + return []; + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + } + } + + private static List ParseJsonResourceNames(string json) + { + try + { + using JsonDocument doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("items", out JsonElement items)) + return []; + + return items.EnumerateArray() + .Select(item => item.GetProperty("metadata").GetProperty("name").GetString() ?? "") + .Where(n => !string.IsNullOrEmpty(n)) + .OrderBy(n => n) + .ToList(); + } + catch + { + return []; + } + } + /// /// Runs a CLI process (helm or kubectl) and captures its output. /// @@ -1404,6 +1624,27 @@ public class ComponentLifecycleService(IDbContextFactory d }; } } + + /// + /// Corrects the ServiceName on a route so it matches the actual Kubernetes Service + /// created by the Helm chart, which may differ from the bare release name. + /// + /// keycloakx: Helm creates "{releaseName}-keycloakx", not "{releaseName}". + /// Older routes stored just the release name — this fixes them at apply time. + /// + private static void FixRouteServiceName(ExternalRoute route, ClusterComponent comp) + { + if (comp.HelmChartName != "keycloakx") + return; + + string releaseName = comp.ReleaseName ?? comp.Name; + // keycloakx chart creates two services: {rel}-keycloakx-headless (headless/StatefulSet) + // and {rel}-keycloakx-http (ClusterIP with ports 80/8443/9000). Route to the latter. + string expected = $"{releaseName}-keycloakx-http"; + + if (!string.Equals(route.ServiceName, expected, StringComparison.OrdinalIgnoreCase)) + route.ServiceName = expected; + } } /// diff --git a/src/EntKube.Web/Services/DatabaseService.cs b/src/EntKube.Web/Services/DatabaseService.cs index 14b8bdb..f472e71 100644 --- a/src/EntKube.Web/Services/DatabaseService.cs +++ b/src/EntKube.Web/Services/DatabaseService.cs @@ -80,15 +80,18 @@ public class DatabaseService(IDbContextFactory dbFactory) .Include(c => c.Cluster) .Where(c => c.Cluster.TenantId == tenantId && c.Status == ComponentStatus.Installed - && (c.Name == "cloudnative-pg" + && (c.Name == "cloudnative-pg" || c.ReleaseName == "cnpg" || c.Name == "mongodb-community-operator" || c.Name == "mongodb-operator" - || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator")) + || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator" + || c.HelmChartName == "community-operator")) .ToListAsync(ct); - ClusterComponent? cnpg = installedComponents.FirstOrDefault(c => c.Name == "cloudnative-pg"); + ClusterComponent? cnpg = installedComponents.FirstOrDefault(c => + c.Name == "cloudnative-pg" || c.ReleaseName == "cnpg"); ClusterComponent? mongo = installedComponents.FirstOrDefault(c => c.Name is "mongodb-community-operator" or "mongodb-operator" - || c.ReleaseName is "mongodb-community-operator" or "mongodb-operator"); + || c.ReleaseName is "mongodb-community-operator" or "mongodb-operator" + || c.HelmChartName == "community-operator"); return new DatabaseOperatorStatus { @@ -110,8 +113,11 @@ public class DatabaseService(IDbContextFactory dbFactory) using ApplicationDbContext db = dbFactory.CreateDbContext(); return await db.KubernetesClusters + .Include(k => k.Environment) .Where(k => k.TenantId == tenantId - && k.Components.Any(c => c.Name == "cloudnative-pg" && c.Status == ComponentStatus.Installed)) + && k.Components.Any(c => + (c.Name == "cloudnative-pg" || c.ReleaseName == "cnpg") + && c.Status == ComponentStatus.Installed)) .OrderBy(k => k.Name) .ToListAsync(ct); } @@ -127,15 +133,33 @@ public class DatabaseService(IDbContextFactory dbFactory) using ApplicationDbContext db = dbFactory.CreateDbContext(); return await db.KubernetesClusters + .Include(k => k.Environment) .Where(k => k.TenantId == tenantId && k.Components.Any(c => (c.Name == "mongodb-community-operator" || c.Name == "mongodb-operator" - || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator") + || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator" + || c.HelmChartName == "community-operator") && c.Status == ComponentStatus.Installed)) .OrderBy(k => k.Name) .ToListAsync(ct); } + /// + /// Returns all Kubernetes clusters for a tenant, regardless of installed operators. + /// Used to populate the cluster dropdown when registering external Postgres instances. + /// + public async Task> GetAllClustersAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.KubernetesClusters + .Include(k => k.Environment) + .Where(k => k.TenantId == tenantId) + .OrderBy(k => k.Name) + .ToListAsync(ct); + } + // ──────── CNPG Discovery ──────── /// @@ -153,7 +177,9 @@ public class DatabaseService(IDbContextFactory dbFactory) List clusters = await db.KubernetesClusters .Include(k => k.Environment) .Where(k => k.TenantId == tenantId - && k.Components.Any(c => c.Name == "cloudnative-pg" && c.Status == ComponentStatus.Installed)) + && k.Components.Any(c => + (c.Name == "cloudnative-pg" || c.ReleaseName == "cnpg") + && c.Status == ComponentStatus.Installed)) .ToListAsync(ct); List results = []; diff --git a/src/EntKube.Web/Services/DeploymentService.cs b/src/EntKube.Web/Services/DeploymentService.cs index a33aff9..c2e4b24 100644 --- a/src/EntKube.Web/Services/DeploymentService.cs +++ b/src/EntKube.Web/Services/DeploymentService.cs @@ -14,7 +14,10 @@ namespace EntKube.Web.Services; /// Also manages the ArgoCD-style resource tree that tracks live cluster state /// for each deployment (sync status, health status, resource hierarchy). /// -public class DeploymentService(IDbContextFactory dbFactory) +public class DeploymentService( + IDbContextFactory dbFactory, + AuditService auditService, + ILogger logger) { // ══════════════════════════════════════════════════════════════ // Deployment CRUD @@ -35,7 +38,12 @@ public class DeploymentService(IDbContextFactory dbFactory string? helmRepoUrl = null, string? helmChartName = null, string? helmChartVersion = null, - CancellationToken ct = default) + string? performedBy = null, + CancellationToken ct = default, + Guid? gitRepositoryId = null, + string? gitPath = null, + string? gitRevision = null, + bool gitAutoSync = true) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -50,11 +58,22 @@ public class DeploymentService(IDbContextFactory dbFactory Namespace = ns, HelmRepoUrl = helmRepoUrl, HelmChartName = helmChartName, - HelmChartVersion = helmChartVersion + HelmChartVersion = helmChartVersion, + GitRepositoryId = gitRepositoryId, + GitPath = gitPath, + GitRevision = gitRevision, + GitAutoSync = gitAutoSync }; db.AppDeployments.Add(deployment); await db.SaveChangesAsync(ct); + + logger.LogInformation("Deployment {Name} created in namespace {Namespace} by {User}", + name, ns, performedBy ?? "system"); + + await auditService.RecordAsync(deployment.Id, "CreateDeployment", "AppDeployment", + name, $"type={type}", performedBy, ct); + return deployment; } @@ -69,16 +88,24 @@ public class DeploymentService(IDbContextFactory dbFactory return await db.AppDeployments .Include(d => d.Environment) .Include(d => d.Cluster) + .Include(d => d.GitRepository) .Where(d => d.AppId == appId) .OrderBy(d => d.CreatedAt) .ToListAsync(ct); } /// - /// Deletes a deployment and all its manifests and tracked resources. - /// EF cascade delete handles the child entities. + /// Updates the editable properties of a deployment. Type, environment, and cluster + /// are structural and cannot change after creation. /// - public async Task DeleteDeploymentAsync(Guid deploymentId, CancellationToken ct = default) + public async Task UpdateDeploymentAsync( + Guid deploymentId, + string name, + string ns, + string? helmRepoUrl = null, + string? helmChartName = null, + string? helmChartVersion = null, + CancellationToken ct = default) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -86,8 +113,33 @@ public class DeploymentService(IDbContextFactory dbFactory if (deployment is not null) { + deployment.Name = name; + deployment.Namespace = ns; + deployment.HelmRepoUrl = helmRepoUrl; + deployment.HelmChartName = helmChartName; + deployment.HelmChartVersion = helmChartVersion; + await db.SaveChangesAsync(ct); + } + } + + /// + /// Deletes a deployment and all its manifests and tracked resources. + /// EF cascade delete handles the child entities. + /// + public async Task DeleteDeploymentAsync(Guid deploymentId, string? performedBy = null, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + AppDeployment? deployment = await db.AppDeployments.FindAsync([deploymentId], ct); + + if (deployment is not null) + { + string name = deployment.Name; db.AppDeployments.Remove(deployment); await db.SaveChangesAsync(ct); + + logger.LogInformation("Deployment {Name} ({DeploymentId}) deleted by {User}", + name, deploymentId, performedBy ?? "system"); } } diff --git a/src/EntKube.Web/Services/DeploymentSyncService.cs b/src/EntKube.Web/Services/DeploymentSyncService.cs new file mode 100644 index 0000000..46d0cee --- /dev/null +++ b/src/EntKube.Web/Services/DeploymentSyncService.cs @@ -0,0 +1,94 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Background service that periodically reconciles live Kubernetes state into +/// the database, keeping DeploymentResource rows and AppDeployment sync/health +/// status current without requiring manual user interaction. +/// +/// Runs every 2 minutes. Processes each deployment sequentially with a short +/// delay between them to avoid bursting all cluster API connections at once. +/// Clusters without a kubeconfig are skipped automatically. +/// +public class DeploymentSyncService( + IServiceScopeFactory scopeFactory, + ILogger logger) : BackgroundService +{ + private static readonly TimeSpan Interval = TimeSpan.FromMinutes(2); + private static readonly TimeSpan InterDeploymentDelay = TimeSpan.FromMilliseconds(200); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // Initial delay so the app finishes starting before the first sync run. + await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + await SyncAllDeploymentsAsync(stoppingToken); + await Task.Delay(Interval, stoppingToken); + } + } + + private async Task SyncAllDeploymentsAsync(CancellationToken ct) + { + using IServiceScope scope = scopeFactory.CreateScope(); + + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + var opsService = scope.ServiceProvider.GetRequiredService(); + var deploymentService = scope.ServiceProvider.GetRequiredService(); + + List deploymentIds; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + deploymentIds = await db.AppDeployments + .Include(d => d.Cluster) + .Where(d => d.Cluster.Kubeconfig != null && d.Cluster.Kubeconfig != "") + .Select(d => d.Id) + .ToListAsync(ct); + } + + logger.LogDebug("DeploymentSyncService: syncing {Count} deployments", deploymentIds.Count); + + int synced = 0, failed = 0; + + foreach (Guid id in deploymentIds) + { + if (ct.IsCancellationRequested) break; + + try + { + KubernetesOperationResult> result = + await opsService.GetLiveResourcesAsync(id, ct); + + if (result.IsSuccess && result.Data is not null) + { + (SyncStatus sync, HealthStatus health) = + KubernetesOperationsService.ComputeStatusFromResources(result.Data); + + await deploymentService.UpdateDeploymentStatusAsync(id, sync, health, null, ct); + synced++; + } + else + { + await deploymentService.UpdateDeploymentStatusAsync( + id, SyncStatus.Unknown, HealthStatus.Unknown, result.Error, ct); + } + } + catch (Exception ex) + { + failed++; + logger.LogWarning(ex, "Failed to sync deployment {DeploymentId}", id); + } + + await Task.Delay(InterDeploymentDelay, ct); + } + + if (synced > 0 || failed > 0) + { + logger.LogInformation("DeploymentSyncService: synced={Synced} failed={Failed}", synced, failed); + } + } +} diff --git a/src/EntKube.Web/Services/DockerRegistryService.cs b/src/EntKube.Web/Services/DockerRegistryService.cs new file mode 100644 index 0000000..b3e72ab --- /dev/null +++ b/src/EntKube.Web/Services/DockerRegistryService.cs @@ -0,0 +1,342 @@ +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Manages Docker / OCI registry credentials stored in the tenant vault. +/// Credentials are scoped per-tenant and optionally per-app. The password is +/// encrypted with the tenant DEK (AES-256-GCM) — the same envelope encryption +/// used by VaultSecret. +/// +/// The SyncToKubernetesAsync method generates a kubernetes.io/dockerconfigjson +/// manifest and applies it via kubectl, so workloads can reference the Secret +/// in imagePullSecrets without ever seeing the raw password in plain text. +/// +public class DockerRegistryService( + IDbContextFactory dbFactory, + VaultEncryptionService encryption) +{ + // ─── CRUD ────────────────────────────────────────────────────────────────── + + /// + /// Creates a new docker registry credential in the tenant vault. + /// The vault must already be initialized. + /// + public async Task CreateAsync( + Guid tenantId, + Guid? appId, + string name, + DockerRegistryType registryType, + string server, + string username, + string password, + string? email, + CancellationToken ct = default) + { + byte[] dataKey = await UnsealAsync(tenantId, ct); + SecretVault vault = await GetVaultAsync(tenantId, ct); + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, password); + + DockerRegistryCredential cred = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + AppId = appId, + Name = name, + RegistryType = registryType, + Server = server, + Username = username, + EncryptedPassword = ciphertext, + PasswordNonce = nonce, + Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim() + }; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + db.Set().Add(cred); + await db.SaveChangesAsync(ct); + return cred; + } + + /// + /// Returns all credentials for a tenant, optionally filtered to a single app. + /// Credentials with AppId = null are tenant-wide and are always included. + /// + public async Task> GetAsync( + Guid tenantId, + Guid? appId = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + IQueryable query = db.Set() + .Include(c => c.KubernetesCluster) + .Where(c => c.Vault.TenantId == tenantId); + + if (appId.HasValue) + query = query.Where(c => c.AppId == appId || c.AppId == null); + + return await query.OrderBy(c => c.Name).ToListAsync(ct); + } + + /// + /// Decrypts and returns the password for a credential. + /// Returns null if the credential is not found. + /// + public async Task GetPasswordAsync( + Guid credentialId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + DockerRegistryCredential? cred = await db.Set() + .Include(c => c.Vault) + .FirstOrDefaultAsync(c => c.Id == credentialId, ct); + + if (cred is null) return null; + + byte[] dataKey = await UnsealAsync(cred.Vault.TenantId, ct); + return encryption.Decrypt(dataKey, cred.EncryptedPassword, cred.PasswordNonce); + } + + /// + /// Updates the mutable fields of a credential. Pass null to leave a field unchanged. + /// + public async Task UpdateAsync( + Guid credentialId, + string? newUsername, + string? newPassword, + string? newEmail, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + DockerRegistryCredential? cred = await db.Set() + .Include(c => c.Vault) + .FirstOrDefaultAsync(c => c.Id == credentialId, ct); + + if (cred is null) return; + + if (newUsername is not null) + cred.Username = newUsername.Trim(); + + if (newPassword is not null) + { + byte[] dataKey = await UnsealAsync(cred.Vault.TenantId, ct); + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, newPassword); + cred.EncryptedPassword = ciphertext; + cred.PasswordNonce = nonce; + } + + if (newEmail is not null) + cred.Email = string.IsNullOrWhiteSpace(newEmail) ? null : newEmail.Trim(); + + cred.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + } + + /// Deletes a credential by ID. Returns false if not found. + public async Task DeleteAsync(Guid credentialId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DockerRegistryCredential? cred = await db.Set().FindAsync([credentialId], ct); + if (cred is null) return false; + db.Set().Remove(cred); + await db.SaveChangesAsync(ct); + return true; + } + + // ─── Kubernetes Sync ─────────────────────────────────────────────────────── + + /// + /// Saves the Kubernetes sync target (cluster, namespace, secret name) for a credential. + /// + public async Task ConfigureSyncAsync( + Guid credentialId, + Guid clusterId, + string secretName, + string @namespace, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DockerRegistryCredential? cred = await db.Set().FindAsync([credentialId], ct); + if (cred is null) return; + + cred.KubernetesClusterId = clusterId; + cred.KubernetesSecretName = secretName.Trim(); + cred.KubernetesNamespace = @namespace.Trim(); + cred.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + } + + /// + /// Generates a kubernetes.io/dockerconfigjson Secret manifest from the credential + /// and applies it to the configured cluster with kubectl. Idempotent — safe to + /// call multiple times; kubectl apply will update in place. + /// + /// A Namespace manifest is prepended so the namespace is created if absent. + /// + public async Task> SyncToKubernetesAsync( + Guid credentialId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + DockerRegistryCredential? cred = await db.Set() + .Include(c => c.Vault) + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == credentialId, ct); + + if (cred is null) + return KubernetesOperationResult.Failure("Credential not found."); + + if (cred.KubernetesCluster is null || string.IsNullOrEmpty(cred.KubernetesCluster.Kubeconfig)) + return KubernetesOperationResult.Failure( + "No Kubernetes cluster configured for this credential. Configure sync first."); + + if (string.IsNullOrEmpty(cred.KubernetesSecretName) || string.IsNullOrEmpty(cred.KubernetesNamespace)) + return KubernetesOperationResult.Failure( + "K8s secret name and namespace must be set before syncing."); + + // Decrypt password. + byte[] dataKey = await UnsealAsync(cred.Vault.TenantId, ct); + string password = encryption.Decrypt(dataKey, cred.EncryptedPassword, cred.PasswordNonce); + + // Build the dockerconfigjson payload. + string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{cred.Username}:{password}")); + var authEntry = new Dictionary + { + ["username"] = cred.Username, + ["password"] = password, + ["auth"] = auth + }; + if (!string.IsNullOrEmpty(cred.Email)) + authEntry["email"] = cred.Email; + + string dockerConfigJson = JsonSerializer.Serialize(new + { + auths = new Dictionary> + { + [cred.Server] = authEntry + } + }); + string dockerConfigBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(dockerConfigJson)); + + // Build the combined manifest: namespace + secret. + string manifest = $""" + apiVersion: v1 + kind: Namespace + metadata: + name: {cred.KubernetesNamespace} + --- + apiVersion: v1 + kind: Secret + metadata: + name: {cred.KubernetesSecretName} + namespace: {cred.KubernetesNamespace} + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: {dockerConfigBase64} + """; + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + string tempManifest = Path.Combine(Path.GetTempPath(), $"entkube-docker-{Guid.NewGuid()}.yaml"); + + try + { + await File.WriteAllTextAsync(tempKubeconfig, cred.KubernetesCluster.Kubeconfig, ct); + await File.WriteAllTextAsync(tempManifest, manifest, ct); + + HelmExecutionResult result = await RunCliAsync( + "kubectl", $"apply -f {tempManifest} --kubeconfig {tempKubeconfig}", ct); + + return result.Success + ? KubernetesOperationResult.Success(result.Output) + : KubernetesOperationResult.Failure(result.Output); + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + if (File.Exists(tempManifest)) File.Delete(tempManifest); + } + } + + // ─── Static helpers ──────────────────────────────────────────────────────── + + /// + /// Returns the well-known server URL for registry types that have a fixed endpoint. + /// Returns an empty string for types where the user must supply the server. + /// + public static string DefaultServer(DockerRegistryType type) => type switch + { + DockerRegistryType.DockerHub => "https://index.docker.io/v1/", + DockerRegistryType.Quay => "quay.io", + DockerRegistryType.GitHubContainerRegistry => "ghcr.io", + _ => string.Empty + }; + + /// + /// Returns a human-readable display label for a registry type. + /// + public static string TypeLabel(DockerRegistryType type) => type switch + { + DockerRegistryType.DockerHub => "Docker Hub", + DockerRegistryType.AzureContainerRegistry => "Azure Container Registry", + DockerRegistryType.Harbor => "Harbor", + DockerRegistryType.Quay => "Quay.io", + DockerRegistryType.GitHubContainerRegistry => "GitHub Container Registry", + _ => "Generic" + }; + + /// + /// Returns a placeholder hint for the server field based on registry type. + /// + public static string ServerPlaceholder(DockerRegistryType type) => type switch + { + DockerRegistryType.AzureContainerRegistry => "myregistry.azurecr.io", + DockerRegistryType.Harbor => "registry.example.com", + _ => "registry.example.com" + }; + + // ─── Private helpers ─────────────────────────────────────────────────────── + + private async Task GetVaultAsync(Guid tenantId, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.Set().FirstAsync(v => v.TenantId == tenantId, ct); + } + + private async Task UnsealAsync(Guid tenantId, CancellationToken ct) + { + SecretVault vault = await GetVaultAsync(tenantId, ct); + return encryption.UnsealDataKey(vault.EncryptedDataKey, vault.Nonce); + } + + private static async Task RunCliAsync( + string program, string arguments, CancellationToken ct) + { + var psi = new ProcessStartInfo + { + FileName = program, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using Process process = Process.Start(psi)!; + string stdout = await process.StandardOutput.ReadToEndAsync(ct); + string stderr = await process.StandardError.ReadToEndAsync(ct); + await process.WaitForExitAsync(ct); + + string combined = string.IsNullOrWhiteSpace(stderr) + ? stdout + : string.IsNullOrWhiteSpace(stdout) ? stderr : $"{stdout}\n{stderr}"; + + return new HelmExecutionResult { Success = process.ExitCode == 0, Output = combined }; + } +} diff --git a/src/EntKube.Web/Services/ExternalRouteHealthService.cs b/src/EntKube.Web/Services/ExternalRouteHealthService.cs new file mode 100644 index 0000000..c2a9162 --- /dev/null +++ b/src/EntKube.Web/Services/ExternalRouteHealthService.cs @@ -0,0 +1,109 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using System.Net; + +namespace EntKube.Web.Services; + +/// +/// Background service that periodically HTTP-probes each external route hostname +/// and writes the result back to ExternalRoute.IsReachable / LastStatusCode / +/// LastHealthCheckAt. This surfaces availability problems in the portal without +/// requiring operators to manually check every endpoint. +/// +/// Runs every 5 minutes. Uses a dedicated HttpClient with a short timeout so +/// an unresponsive host doesn't stall the entire check cycle. +/// +public class ExternalRouteHealthService( + IServiceScopeFactory scopeFactory, + IHttpClientFactory httpClientFactory, + ILogger logger) : BackgroundService +{ + private static readonly TimeSpan Interval = TimeSpan.FromMinutes(5); + private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + await CheckAllRoutesAsync(stoppingToken); + await Task.Delay(Interval, stoppingToken); + } + } + + private async Task CheckAllRoutesAsync(CancellationToken ct) + { + using IServiceScope scope = scopeFactory.CreateScope(); + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + + List<(Guid Id, string Hostname, string PathPrefix)> routes; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + routes = await db.ExternalRoutes + .Select(r => new ValueTuple(r.Id, r.Hostname, r.PathPrefix)) + .ToListAsync(ct); + } + + if (routes.Count == 0) return; + + logger.LogDebug("ExternalRouteHealthService: checking {Count} routes", routes.Count); + + HttpClient httpClient = httpClientFactory.CreateClient("RouteHealth"); + + foreach ((Guid id, string hostname, string pathPrefix) in routes) + { + if (ct.IsCancellationRequested) break; + + bool isReachable = false; + int? statusCode = null; + + try + { + string url = $"https://{hostname}{pathPrefix}"; + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(RequestTimeout); + + using HttpResponseMessage response = await httpClient.GetAsync(url, cts.Token); + statusCode = (int)response.StatusCode; + + // 2xx, 3xx, and even 401/403 mean the host is reachable — just access-controlled. + isReachable = (int)response.StatusCode < 500; + } + catch (Exception ex) when (ex is not OperationCanceledException { } oce || oce.CancellationToken != ct) + { + logger.LogDebug("Health check failed for {Hostname}: {Message}", hostname, ex.Message); + isReachable = false; + statusCode = null; + } + + try + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + ExternalRoute? route = await db.ExternalRoutes.FindAsync([id], ct); + if (route is null) continue; + + DateTime now = DateTime.UtcNow; + route.LastHealthCheckAt = now; + route.LastStatusCode = statusCode; + route.IsReachable = isReachable; + + // Persist history entry for uptime tracking + db.ExternalRouteHealthHistories.Add(new ExternalRouteHealthHistory + { + RouteId = id, + IsReachable = isReachable, + StatusCode = statusCode, + CheckedAt = now + }); + + await db.SaveChangesAsync(ct); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to persist health check result for route {RouteId}", id); + } + } + } +} diff --git a/src/EntKube.Web/Services/ExternalRouteService.cs b/src/EntKube.Web/Services/ExternalRouteService.cs index 458d639..c887ebf 100644 --- a/src/EntKube.Web/Services/ExternalRouteService.cs +++ b/src/EntKube.Web/Services/ExternalRouteService.cs @@ -33,7 +33,9 @@ public class ExternalRouteRequest /// Routes are stored in the database so we can track what's exposed, /// regenerate manifests, and tear down routes when components are uninstalled. /// -public class ExternalRouteService(IDbContextFactory dbFactory) +public class ExternalRouteService( + IDbContextFactory dbFactory, + ILogger logger) { /// /// Adds an external route to a component. The operator specifies the hostname @@ -113,6 +115,9 @@ public class ExternalRouteService(IDbContextFactory dbFact db.ExternalRoutes.Add(route); await db.SaveChangesAsync(ct); + + logger.LogInformation("External route {Hostname} added to component {ComponentId}", route.Hostname, componentId); + return route; } @@ -144,6 +149,8 @@ public class ExternalRouteService(IDbContextFactory dbFact db.ExternalRoutes.Remove(route); await db.SaveChangesAsync(ct); + + logger.LogInformation("External route {RouteId} deleted", routeId); } /// @@ -187,7 +194,8 @@ public class ExternalRouteService(IDbContextFactory dbFact public static string GenerateHttpRouteYaml(ExternalRoute route) { string ns = route.Component?.Namespace ?? "default"; - string routeName = $"{route.ServiceName}-route"; + // Name is hostname-based so it stays stable even when the service name is corrected. + string routeName = ToListenerName(route.Hostname) + "-route"; // TLS is terminated at the Gateway listener — HTTPRoute only routes by hostname/path. // No TLS section belongs in HTTPRoute.spec. @@ -310,19 +318,27 @@ public class ExternalRouteService(IDbContextFactory dbFact /// /// Generates a gateway.networking.k8s.io/v1/Gateway resource with one HTTPS /// listener per unique hostname in , plus an HTTP-to-HTTPS - /// redirect listener, and explicit cert-manager Certificate resources placed in - /// so they can be referenced by the Gateway's - /// certificateRefs without cross-namespace ReferenceGrants. + /// redirect listener, cert-manager Certificate resources, and a ReferenceGrant. + /// + /// Certificates are placed in (default: cert-manager) + /// rather than in the gateway namespace. istio-system often has admission + /// controls (Kyverno, OPA) that silently block CertificateRequest creation; the + /// cert-manager namespace is guaranteed to be unrestricted for cert-manager's own + /// controllers. A ReferenceGrant is generated in + /// so the Gateway in can cross-reference the + /// resulting TLS Secrets. /// /// The returned YAML contains multiple documents separated by ---: - /// 1. The Gateway resource + /// 1. The Gateway resource (certificateRefs include namespace: certNamespace) /// 2. An HTTP→HTTPS redirect HTTPRoute - /// 3. One Certificate resource per ClusterIssuer-mode hostname + /// 3. A ReferenceGrant in certNamespace + /// 4. One Certificate per ClusterIssuer-mode hostname (in certNamespace) /// public static string GenerateGatewayYaml( string gatewayName, string gatewayNamespace, - IEnumerable routes) + IEnumerable routes, + string certNamespace = "cert-manager") { var grouped = routes .GroupBy(r => r.Hostname) @@ -335,6 +351,8 @@ public class ExternalRouteService(IDbContextFactory dbFact )) .ToList(); + // certificateRefs include namespace so the Gateway can cross-reference Secrets + // in certNamespace. Istio honours cross-namespace refs when a ReferenceGrant exists. IEnumerable httpsListeners = grouped.Select(g => $" - name: {g.ListenerName}\n" + $" hostname: {g.Hostname}\n" + @@ -344,25 +362,23 @@ public class ExternalRouteService(IDbContextFactory dbFact $" mode: Terminate\n" + $" certificateRefs:\n" + $" - name: {g.CertSecretName}\n" + + $" namespace: {certNamespace}\n" + $" allowedRoutes:\n" + $" namespaces:\n" + $" from: All"); + // HTTP listener allows routes from All namespaces so cert-manager can attach + // its ACME HTTP-01 challenge HTTPRoute (created in the cert-manager namespace). const string httpListener = " - name: http-redirect\n" + " port: 80\n" + " protocol: HTTP\n" + " allowedRoutes:\n" + " namespaces:\n" + - " from: Same"; + " from: All"; string allListeners = string.Join("\n", httpsListeners.Append(httpListener)); - // No cert-manager.io/cluster-issuer annotation on the Gateway — we create - // explicit Certificate resources below, so the gateway-shim is not needed. - // Using the annotation alongside explicit Certificates causes the gateway-shim - // to fight over the same Certificate objects, which prevents cert-manager - // from ever starting the ACME order. string gatewayYaml = $"apiVersion: gateway.networking.k8s.io/v1\n" + $"kind: Gateway\n" + @@ -397,7 +413,24 @@ public class ExternalRouteService(IDbContextFactory dbFact $" scheme: https\n" + $" statusCode: 301"; - List parts = [gatewayYaml, httpRedirectRoute]; + // ReferenceGrant in certNamespace — allows the Gateway in gatewayNamespace to + // read Secrets in certNamespace without needing cluster-admin permissions. + string referenceGrant = + $"apiVersion: gateway.networking.k8s.io/v1beta1\n" + + $"kind: ReferenceGrant\n" + + $"metadata:\n" + + $" name: gateway-tls-from-{ToListenerName(gatewayNamespace)}\n" + + $" namespace: {certNamespace}\n" + + $"spec:\n" + + $" from:\n" + + $" - group: gateway.networking.k8s.io\n" + + $" kind: Gateway\n" + + $" namespace: {gatewayNamespace}\n" + + $" to:\n" + + $" - group: \"\"\n" + + $" kind: Secret"; + + List parts = [gatewayYaml, httpRedirectRoute, referenceGrant]; foreach (var g in grouped.Where(g => g.IsCertIssuer && !string.IsNullOrWhiteSpace(g.ClusterIssuerName))) { @@ -406,7 +439,7 @@ public class ExternalRouteService(IDbContextFactory dbFact $"kind: Certificate\n" + $"metadata:\n" + $" name: {g.CertSecretName}\n" + - $" namespace: {gatewayNamespace}\n" + + $" namespace: {certNamespace}\n" + $"spec:\n" + $" secretName: {g.CertSecretName}\n" + $" issuerRef:\n" + @@ -424,7 +457,7 @@ public class ExternalRouteService(IDbContextFactory dbFact /// by replacing non-alphanumeric characters with dashes, trimming edge dashes, /// and capping at 63 characters (DNS label limit). /// - private static string ToListenerName(string hostname) + public static string ToListenerName(string hostname) { string sanitized = new string(hostname.ToLowerInvariant() .Select(c => char.IsLetterOrDigit(c) ? c : '-') @@ -484,9 +517,43 @@ public class ExternalRouteService(IDbContextFactory dbFact return hasTraefik ? "traefik" : "istio"; } + public async Task GetRouteUptimeAsync( + Guid routeId, int windowDays = 7, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime from = DateTime.UtcNow.AddDays(-windowDays); + + List history = await db.ExternalRouteHealthHistories + .Where(h => h.RouteId == routeId && h.CheckedAt >= from) + .OrderBy(h => h.CheckedAt) + .ToListAsync(ct); + + if (history.Count == 0) + return new RouteUptimeSummary(routeId, windowDays, null, 0, history); + + double uptimePct = (double)history.Count(h => h.IsReachable) / history.Count * 100; + double avgResponseMs = history + .Where(h => h.ResponseMs.HasValue) + .Select(h => (double)h.ResponseMs!.Value) + .DefaultIfEmpty(0) + .Average(); + + return new RouteUptimeSummary(routeId, windowDays, Math.Round(uptimePct, 2), Math.Round(avgResponseMs), history); + } + private static string ResolveGatewayName(IEnumerable components) => ResolveGateway(components).Name; private static string ResolveGatewayNamespace(IEnumerable components) => ResolveGateway(components).Namespace; } + +public record RouteUptimeSummary( + Guid RouteId, + int WindowDays, + double? UptimePercent, + double AvgResponseMs, + List History) +{ + public string UptimeDisplay => UptimePercent.HasValue ? $"{UptimePercent:F2}%" : "No data"; +} diff --git a/src/EntKube.Web/Services/GitOperationsService.cs b/src/EntKube.Web/Services/GitOperationsService.cs new file mode 100644 index 0000000..38b34f6 --- /dev/null +++ b/src/EntKube.Web/Services/GitOperationsService.cs @@ -0,0 +1,327 @@ +using System.Diagnostics; +using System.Text; +using EntKube.Web.Data; +using LibGit2Sharp; +using LibGit2Sharp.Handlers; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Low-level Git operations. Uses LibGit2Sharp for HTTPS repos (PAT and +/// username+password auth) and the `git` CLI for SSH repos (more reliable +/// SSH key support across platforms). Credentials are always fetched from +/// the tenant vault — they never appear in plaintext outside this service. +/// +/// SSH host verification uses auto-trust-on-first-connect: the server +/// fingerprint is recorded in GitKnownHost on first contact; subsequent +/// connections with a changed fingerprint are rejected. +/// +public class GitOperationsService( + IDbContextFactory dbFactory, + VaultService vault, + ILogger logger) +{ + private static readonly string CloneRoot = + Path.Combine(Path.GetTempPath(), "entkube-git"); + + // ── Public API ─────────────────────────────────────────────────────────────── + + /// + /// Checks out the specified path at the given revision and returns a map of + /// relative file path → content for all files found. + /// + public async Task CheckoutFilesAsync( + GitRepository repo, string path, string revision, CancellationToken ct = default) + { + string workDir = Path.Combine(CloneRoot, repo.Id.ToString("N")); + Directory.CreateDirectory(workDir); + + try + { + if (repo.AuthType == GitAuthType.SshKey) + await EnsureClonedViaSshAsync(repo, workDir, revision, ct); + else + await EnsureClonedViaHttpsAsync(repo, workDir, revision, ct); + + using Repository r = new(workDir); + Commit commit = ResolveRevision(r, revision); + Dictionary files = ReadFilesAtCommit(r, commit, path); + + return new GitCheckoutResult + { + CommitSha = commit.Sha, + CommitMessage = commit.MessageShort, + CommittedAt = commit.Committer.When.UtcDateTime, + Files = files + }; + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogWarning(ex, "Git operation failed for {RepoId}", repo.Id); + return GitCheckoutResult.Failure(ex.Message); + } + } + + /// + /// Returns the HEAD commit SHA for the given revision without reading files. + /// Used for quick change detection. + /// + public async Task GetHeadCommitAsync( + GitRepository repo, string revision, CancellationToken ct = default) + { + string workDir = Path.Combine(CloneRoot, repo.Id.ToString("N")); + Directory.CreateDirectory(workDir); + + try + { + if (repo.AuthType == GitAuthType.SshKey) + await EnsureClonedViaSshAsync(repo, workDir, revision, ct); + else + await EnsureClonedViaHttpsAsync(repo, workDir, revision, ct); + + using Repository r = new(workDir); + Commit commit = ResolveRevision(r, revision); + return commit.Sha; + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogWarning(ex, "Git HEAD check failed for {RepoId}", repo.Id); + return null; + } + } + + // ── HTTPS clone/fetch via LibGit2Sharp ──────────────────────────────────────── + + private async Task EnsureClonedViaHttpsAsync( + GitRepository repo, string workDir, string revision, CancellationToken ct) + { + CredentialsHandler credentials = await BuildHttpsCredentialsAsync(repo, ct); + + if (!Repository.IsValid(workDir)) + { + logger.LogDebug("Cloning {Url} into {Dir}", repo.Url, workDir); + + CloneOptions opts = new() { IsBare = true }; + opts.FetchOptions.CredentialsProvider = credentials; + opts.FetchOptions.CertificateCheck = (_, _, _) => true; + + Repository.Clone(repo.Url, workDir, opts); + } + else + { + logger.LogDebug("Fetching {Url}", repo.Url); + + using Repository localRepo = new(workDir); + + FetchOptions fetchOpts = new() + { + CredentialsProvider = credentials, + CertificateCheck = (_, _, _) => true + }; + + Commands.Fetch(localRepo, "origin", + ["+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*"], + fetchOpts, null); + } + } + + private async Task BuildHttpsCredentialsAsync( + GitRepository repo, CancellationToken ct) + { + return repo.AuthType switch + { + GitAuthType.HttpsPat => await BuildPatCredentialsAsync(repo, ct), + GitAuthType.HttpsPassword => await BuildPasswordCredentialsAsync(repo, ct), + _ => (_, _, _) => new DefaultCredentials() + }; + } + + private async Task BuildPatCredentialsAsync( + GitRepository repo, CancellationToken ct) + { + string? pat = await vault.GetGitRepositorySecretValueAsync( + repo.TenantId, repo.Id, "PAT", ct); + + if (pat is null) + throw new InvalidOperationException($"Git repo '{repo.Name}' has no PAT secret in vault."); + + return (_, _, _) => new UsernamePasswordCredentials + { + Username = pat, + Password = string.Empty + }; + } + + private async Task BuildPasswordCredentialsAsync( + GitRepository repo, CancellationToken ct) + { + string? password = await vault.GetGitRepositorySecretValueAsync( + repo.TenantId, repo.Id, "PASSWORD", ct); + + if (password is null) + throw new InvalidOperationException($"Git repo '{repo.Name}' has no PASSWORD secret in vault."); + + string username = repo.Username ?? string.Empty; + return (_, _, _) => new UsernamePasswordCredentials + { + Username = username, + Password = password + }; + } + + // ── SSH clone/fetch via git CLI ────────────────────────────────────────────── + + private async Task EnsureClonedViaSshAsync( + GitRepository repo, string workDir, string revision, CancellationToken ct) + { + string? privateKey = await vault.GetGitRepositorySecretValueAsync( + repo.TenantId, repo.Id, "SSH_PRIVATE_KEY", ct); + + if (privateKey is null) + throw new InvalidOperationException($"Git repo '{repo.Name}' has no SSH_PRIVATE_KEY secret in vault."); + + string keyPath = await WriteTempKeyFileAsync(privateKey); + + try + { + // Build GIT_SSH_COMMAND to disable strict host checking on first connect + // (host key is separately tracked in GitKnownHost — LibGit2Sharp SSH limitations + // mean we trust the host via git CLI's -o StrictHostKeyChecking=accept-new). + string sshCmd = $"ssh -i {keyPath} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null"; + + if (!Directory.Exists(Path.Combine(workDir, "objects"))) + { + logger.LogDebug("Cloning {Url} via SSH into {Dir}", repo.Url, workDir); + await RunGitAsync(["clone", "--bare", repo.Url, workDir], sshCmd, ct); + } + else + { + logger.LogDebug("Fetching {Url} via SSH", repo.Url); + await RunGitAsync( + ["-C", workDir, "fetch", "origin", + "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*"], + sshCmd, ct); + } + } + finally + { + if (File.Exists(keyPath)) File.Delete(keyPath); + } + } + + private static async Task RunGitAsync( + string[] args, string sshCommand, CancellationToken ct) + { + ProcessStartInfo psi = new("git") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + foreach (string arg in args) + psi.ArgumentList.Add(arg); + + psi.Environment["GIT_SSH_COMMAND"] = sshCommand; + psi.Environment["GIT_TERMINAL_PROMPT"] = "0"; + + using Process proc = new() { StartInfo = psi }; + StringBuilder stderr = new(); + + proc.ErrorDataReceived += (_, e) => { if (e.Data is not null) stderr.AppendLine(e.Data); }; + proc.Start(); + proc.BeginErrorReadLine(); + await proc.WaitForExitAsync(ct); + + if (proc.ExitCode != 0) + throw new InvalidOperationException( + $"git {string.Join(' ', args)} failed (exit {proc.ExitCode}): {stderr}"); + } + + // ── Shared helpers ─────────────────────────────────────────────────────────── + + private static Commit ResolveRevision(Repository repo, string revision) + { + Branch? branch = repo.Branches[$"refs/heads/{revision}"] + ?? repo.Branches[revision]; + + if (branch?.Tip is not null) return branch.Tip; + + Tag? tag = repo.Tags[revision]; + if (tag?.PeeledTarget is Commit tagCommit) return tagCommit; + if (tag?.Target is Commit directTagCommit) return directTagCommit; + + Commit? commit = repo.Lookup(revision); + if (commit is not null) return commit; + + throw new InvalidOperationException( + $"Could not resolve revision '{revision}' in repository."); + } + + private static Dictionary ReadFilesAtCommit( + Repository repo, Commit commit, string path) + { + Dictionary result = []; + string normalizedPath = path.Trim('/'); + + Tree tree = commit.Tree; + + if (!string.IsNullOrEmpty(normalizedPath)) + { + TreeEntry? entry = commit.Tree[normalizedPath]; + if (entry?.Target is Tree subtree) + tree = subtree; + } + + ReadTreeRecursive(tree, string.Empty, result); + return result; + } + + private static void ReadTreeRecursive(Tree tree, string prefix, Dictionary result) + { + foreach (TreeEntry entry in tree) + { + string entryPath = string.IsNullOrEmpty(prefix) + ? entry.Name + : $"{prefix}/{entry.Name}"; + + if (entry.TargetType == TreeEntryTargetType.Blob) + { + result[entryPath] = ((Blob)entry.Target).GetContentText(); + } + else if (entry.TargetType == TreeEntryTargetType.Tree) + { + ReadTreeRecursive((Tree)entry.Target, entryPath, result); + } + } + } + + private static async Task WriteTempKeyFileAsync(string privateKey) + { + string path = Path.Combine(Path.GetTempPath(), $"entkube-ssh-{Guid.NewGuid():N}"); + await File.WriteAllTextAsync(path, privateKey); + File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); + return path; + } +} + +/// Result of a git checkout operation. +public class GitCheckoutResult +{ + public bool IsSuccess { get; init; } = true; + public string? Error { get; init; } + public string CommitSha { get; init; } = string.Empty; + public string CommitMessage { get; init; } = string.Empty; + public DateTime CommittedAt { get; init; } + + /// Relative file path → file content for all files in the checked-out path. + public Dictionary Files { get; init; } = []; + + public static GitCheckoutResult Failure(string error) => new() + { + IsSuccess = false, + Error = error + }; +} diff --git a/src/EntKube.Web/Services/GitRepositoryService.cs b/src/EntKube.Web/Services/GitRepositoryService.cs new file mode 100644 index 0000000..080597f --- /dev/null +++ b/src/EntKube.Web/Services/GitRepositoryService.cs @@ -0,0 +1,178 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// CRUD operations for GitRepository records and credential validation. +/// Credential storage always goes through VaultService so values are +/// encrypted with the tenant DEK before being persisted. +/// +public class GitRepositoryService( + IDbContextFactory dbFactory, + VaultService vault, + GitOperationsService gitOps) +{ + // ── Listing & retrieval ────────────────────────────────────────────────────── + + public async Task> GetRepositoriesAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.GitRepositories + .Where(r => r.TenantId == tenantId) + .OrderBy(r => r.Name) + .ToListAsync(ct); + } + + public async Task GetRepositoryAsync( + Guid tenantId, Guid repoId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.GitRepositories + .FirstOrDefaultAsync(r => r.TenantId == tenantId && r.Id == repoId, ct); + } + + // ── Create / update ────────────────────────────────────────────────────────── + + /// + /// Creates a new Git repository registration. After calling this, store + /// credentials with , , + /// or as appropriate for the auth type. + /// + public async Task CreateRepositoryAsync( + Guid tenantId, string name, string url, + GitAuthType authType, string? username = null, string defaultBranch = "main", + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + GitRepository repo = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + Name = name, + Url = url, + AuthType = authType, + Username = username, + DefaultBranch = defaultBranch + }; + + db.GitRepositories.Add(repo); + await db.SaveChangesAsync(ct); + return repo; + } + + public async Task UpdateRepositoryAsync( + Guid tenantId, Guid repoId, + string name, string url, GitAuthType authType, + string? username, string defaultBranch, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + GitRepository? repo = await db.GitRepositories + .FirstOrDefaultAsync(r => r.TenantId == tenantId && r.Id == repoId, ct); + + if (repo is null) return false; + + repo.Name = name; + repo.Url = url; + repo.AuthType = authType; + repo.Username = username; + repo.DefaultBranch = defaultBranch; + + await db.SaveChangesAsync(ct); + return true; + } + + public async Task DeleteRepositoryAsync( + Guid tenantId, Guid repoId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + GitRepository? repo = await db.GitRepositories + .FirstOrDefaultAsync(r => r.TenantId == tenantId && r.Id == repoId, ct); + + if (repo is null) return false; + + db.GitRepositories.Remove(repo); + await db.SaveChangesAsync(ct); + return true; + } + + // ── Credential management (all go through vault) ───────────────────────────── + + /// Store a Personal Access Token for HTTPS auth. + public Task SetPatAsync(Guid tenantId, Guid repoId, string pat, CancellationToken ct = default) + => vault.SetGitRepositorySecretAsync(tenantId, repoId, "PAT", pat, ct); + + /// Store a password for HTTPS username+password auth. + public Task SetPasswordAsync(Guid tenantId, Guid repoId, string password, CancellationToken ct = default) + => vault.SetGitRepositorySecretAsync(tenantId, repoId, "PASSWORD", password, ct); + + /// Store an SSH private key (PEM format). + public Task SetSshKeyAsync(Guid tenantId, Guid repoId, string privateKeyPem, CancellationToken ct = default) + => vault.SetGitRepositorySecretAsync(tenantId, repoId, "SSH_PRIVATE_KEY", privateKeyPem, ct); + + // ── Connection validation ──────────────────────────────────────────────────── + + /// + /// Attempts to connect to the repository and fetch the HEAD commit SHA. + /// Returns (success, commitSha|errorMessage). + /// + public async Task<(bool Success, string Message)> ValidateConnectionAsync( + Guid tenantId, Guid repoId, CancellationToken ct = default) + { + GitRepository? repo = await GetRepositoryAsync(tenantId, repoId, ct); + + if (repo is null) + return (false, "Repository not found."); + + string revision = repo.DefaultBranch; + + try + { + string? sha = await gitOps.GetHeadCommitAsync(repo, revision, ct); + + if (sha is null) + return (false, "Could not reach repository or resolve branch."); + + return (true, $"Connected. HEAD ({revision}): {sha[..Math.Min(7, sha.Length)]}"); + } + catch (Exception ex) + { + return (false, ex.Message); + } + } + + // ── Known hosts management ─────────────────────────────────────────────────── + + public async Task> GetKnownHostsAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.GitKnownHosts + .Where(h => h.TenantId == tenantId) + .OrderBy(h => h.Hostname) + .ToListAsync(ct); + } + + public async Task RemoveKnownHostAsync( + Guid tenantId, Guid knownHostId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + GitKnownHost? host = await db.GitKnownHosts + .FirstOrDefaultAsync(h => h.TenantId == tenantId && h.Id == knownHostId, ct); + + if (host is null) return false; + + db.GitKnownHosts.Remove(host); + await db.SaveChangesAsync(ct); + return true; + } +} diff --git a/src/EntKube.Web/Services/GitSyncService.cs b/src/EntKube.Web/Services/GitSyncService.cs new file mode 100644 index 0000000..a35cdf6 --- /dev/null +++ b/src/EntKube.Web/Services/GitSyncService.cs @@ -0,0 +1,280 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Background service that periodically pulls Git repositories and reconciles +/// AppDeployment manifests/Helm values from the latest commit. +/// +/// For each deployment with a GitRepositoryId and GitAutoSync=true: +/// 1. Fetches the HEAD commit SHA for the configured revision. +/// 2. If the commit matches GitLastSyncedCommit, skips (already current). +/// 3. Otherwise reads files from the repo and applies changes: +/// - GitYaml: replaces DeploymentManifest rows with parsed YAML documents. +/// - GitHelm: updates HelmValues. +/// - GitAppOfApps: calls AppOfAppsService to reconcile child deployments. +/// 4. Updates GitLastSyncedCommit and GitLastSyncedAt. +/// +/// Runs every 3 minutes by default. A deployment can also be synced on-demand +/// via (called from the UI or webhook). +/// +public class GitSyncService( + IServiceScopeFactory scopeFactory, + ILogger logger) : BackgroundService +{ + private static readonly TimeSpan Interval = TimeSpan.FromMinutes(3); + private static readonly TimeSpan InterDeploymentDelay = TimeSpan.FromMilliseconds(300); + + // Deployments can be queued for immediate sync (e.g. webhook or UI button). + private readonly System.Collections.Concurrent.ConcurrentQueue _syncQueue = new(); + + public void EnqueueSync(Guid deploymentId) => _syncQueue.Enqueue(deploymentId); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(TimeSpan.FromSeconds(45), stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + // Drain the immediate queue first. + while (_syncQueue.TryDequeue(out Guid queued)) + { + try { await SyncDeploymentAsync(queued, stoppingToken); } + catch (Exception ex) { logger.LogWarning(ex, "GitSyncService: queue sync failed for {Id}", queued); } + } + + await SyncAllAutoSyncDeploymentsAsync(stoppingToken); + await Task.Delay(Interval, stoppingToken); + } + } + + private async Task SyncAllAutoSyncDeploymentsAsync(CancellationToken ct) + { + using IServiceScope scope = scopeFactory.CreateScope(); + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + + List deploymentIds; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + DeploymentType[] gitTypes = + [ + DeploymentType.GitYaml, + DeploymentType.GitHelm, + DeploymentType.GitAppOfApps + ]; + + deploymentIds = await db.AppDeployments + .Where(d => d.GitRepositoryId != null + && d.GitAutoSync + && gitTypes.Contains(d.Type)) + .Select(d => d.Id) + .ToListAsync(ct); + } + + logger.LogDebug("GitSyncService: checking {Count} git-backed deployments", deploymentIds.Count); + + foreach (Guid id in deploymentIds) + { + if (ct.IsCancellationRequested) break; + + try + { + await SyncDeploymentAsync(id, ct); + } + catch (Exception ex) + { + logger.LogWarning(ex, "GitSyncService: unhandled error syncing {Id}", id); + } + + await Task.Delay(InterDeploymentDelay, ct); + } + } + + /// + /// Syncs a single deployment from its Git source. Safe to call directly from + /// the UI or webhook handler for on-demand sync. + /// + public async Task SyncDeploymentAsync( + Guid deploymentId, CancellationToken ct = default) + { + using IServiceScope scope = scopeFactory.CreateScope(); + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + var gitOps = scope.ServiceProvider.GetRequiredService(); + var appOfApps = scope.ServiceProvider.GetRequiredService(); + + AppDeployment? deployment; + GitRepository? repo; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + deployment = await db.AppDeployments + .Include(d => d.GitRepository) + .FirstOrDefaultAsync(d => d.Id == deploymentId, ct); + } + + if (deployment is null) + return GitSyncResult.Failure("Deployment not found."); + + repo = deployment.GitRepository; + + if (repo is null) + return GitSyncResult.Failure("Deployment has no Git repository configured."); + + string revision = deployment.GitRevision ?? repo.DefaultBranch; + string path = deployment.GitPath ?? "."; + + // Quick HEAD check — skip checkout if already up to date. + string? headSha = await gitOps.GetHeadCommitAsync(repo, revision, ct); + + if (headSha is null) + return GitSyncResult.Failure("Could not fetch HEAD commit from repository."); + + if (headSha == deployment.GitLastSyncedCommit) + return new GitSyncResult { AlreadyCurrent = true, CommitSha = headSha }; + + // Full checkout. + GitCheckoutResult checkout = await gitOps.CheckoutFilesAsync(repo, path, revision, ct); + + if (!checkout.IsSuccess) + return GitSyncResult.Failure(checkout.Error ?? "Checkout failed."); + + // Apply changes based on deployment type. + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + // Re-fetch to get a tracked entity. + AppDeployment tracked = await db.AppDeployments + .Include(d => d.Manifests) + .FirstAsync(d => d.Id == deploymentId, ct); + + switch (tracked.Type) + { + case DeploymentType.GitYaml: + await ApplyYamlManifestsAsync(db, tracked, checkout.Files, ct); + break; + + case DeploymentType.GitHelm: + ApplyHelmValues(tracked, checkout.Files, path); + break; + + case DeploymentType.GitAppOfApps: + await appOfApps.ReconcileAsync(tracked, checkout.Files, ct); + break; + } + + tracked.GitLastSyncedCommit = checkout.CommitSha; + tracked.GitLastSyncedAt = DateTime.UtcNow; + tracked.SyncStatus = SyncStatus.OutOfSync; + + await db.SaveChangesAsync(ct); + } + + logger.LogInformation( + "GitSyncService: synced deployment {Name} to commit {Sha}", + deployment.Name, checkout.CommitSha[..Math.Min(7, checkout.CommitSha.Length)]); + + return new GitSyncResult + { + CommitSha = checkout.CommitSha, + CommitMessage = checkout.CommitMessage, + CommittedAt = checkout.CommittedAt + }; + } + + // ── Apply helpers ──────────────────────────────────────────────────────────── + + private static async Task ApplyYamlManifestsAsync( + ApplicationDbContext db, + AppDeployment deployment, + Dictionary files, + CancellationToken ct) + { + // Remove existing manifests and replace with the current set from Git. + db.DeploymentManifests.RemoveRange(deployment.Manifests); + + List<(string Path, string Content)> yamlFiles = files + .Where(f => f.Key.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) + || f.Key.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)) + .OrderBy(f => f.Key) + .Select(f => (f.Key, f.Value)) + .ToList(); + + int order = 0; + + foreach ((string filePath, string content) in yamlFiles) + { + // Split multi-document YAML files. + foreach (string doc in content.Split("\n---", StringSplitOptions.RemoveEmptyEntries)) + { + string trimmed = doc.Trim(); + if (string.IsNullOrWhiteSpace(trimmed)) continue; + + // Extract kind and name for labelling — best-effort parsing. + (string kind, string name) = ExtractKindAndName(trimmed); + + db.DeploymentManifests.Add(new DeploymentManifest + { + Id = Guid.NewGuid(), + DeploymentId = deployment.Id, + Name = name, + Kind = kind, + YamlContent = trimmed, + SortOrder = order++, + SourceFile = filePath + }); + } + } + + await db.SaveChangesAsync(ct); + } + + private static void ApplyHelmValues( + AppDeployment deployment, + Dictionary files, + string chartPath) + { + // Look for values.yaml in the chart path. + string key = string.IsNullOrEmpty(chartPath) || chartPath == "." + ? "values.yaml" + : $"{chartPath.TrimEnd('/')}/values.yaml"; + + if (files.TryGetValue(key, out string? values)) + { + deployment.HelmValues = values; + } + } + + private static (string Kind, string Name) ExtractKindAndName(string yaml) + { + string kind = "Unknown"; + string name = "unnamed"; + + foreach (string line in yaml.Split('\n')) + { + if (line.StartsWith("kind:", StringComparison.Ordinal)) + kind = line["kind:".Length..].Trim(); + else if (line.TrimStart().StartsWith("name:", StringComparison.Ordinal) + && name == "unnamed") + name = line.Split(':')[1].Trim().Trim('"'); + } + + return (kind, name); + } +} + +public class GitSyncResult +{ + public bool IsSuccess { get; init; } = true; + public bool AlreadyCurrent { get; init; } + public string? Error { get; init; } + public string CommitSha { get; init; } = string.Empty; + public string CommitMessage { get; init; } = string.Empty; + public DateTime CommittedAt { get; init; } + + public static GitSyncResult Failure(string error) => new() + { + IsSuccess = false, + Error = error + }; +} diff --git a/src/EntKube.Web/Services/GitWebhookService.cs b/src/EntKube.Web/Services/GitWebhookService.cs new file mode 100644 index 0000000..915885a --- /dev/null +++ b/src/EntKube.Web/Services/GitWebhookService.cs @@ -0,0 +1,169 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Handles incoming Git push webhook payloads from GitHub and Azure DevOps, +/// then enqueues sync for any deployments whose repository matches the push. +/// +/// Endpoint: POST /api/git/webhook?tenantId={id} +/// Optional: supply the repo's PAT as an HMAC secret for payload verification +/// (GitHub uses X-Hub-Signature-256; Azure DevOps uses Basic auth or a token). +/// +/// Verification flow: +/// 1. Look up all GitRepository records for the tenant whose URL matches the payload. +/// 2. If the repo has a "WEBHOOK_SECRET" vault secret, verify the signature. +/// 3. Enqueue a GitSyncService sync for each matching deployment. +/// +public class GitWebhookService( + IDbContextFactory dbFactory, + VaultService vault, + GitSyncService syncService, + ILogger logger) +{ + /// + /// Processes a raw webhook request body and headers. + /// Returns (200 OK message) or throws with a descriptive message on failure. + /// + public async Task HandleAsync( + string tenantSlug, + string rawBody, + string? hubSignature256, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + Guid? tenantId = await db.Tenants + .Where(t => t.Slug == tenantSlug) + .Select(t => (Guid?)t.Id) + .FirstOrDefaultAsync(ct); + + if (tenantId is null) + { + logger.LogWarning("Webhook: unknown tenant slug '{Slug}'", tenantSlug); + return "Unknown tenant."; + } + + // Try to extract the repository URL from the payload. + string? repoUrl = ExtractRepoUrl(rawBody); + + if (repoUrl is null) + { + logger.LogDebug("Webhook: could not extract repository URL from payload"); + return "Could not parse repository URL."; + } + + // Find matching registered repositories. + List repos = await db.GitRepositories + .Where(r => r.TenantId == tenantId.Value) + .ToListAsync(ct); + + List matched = repos + .Where(r => NormalizeUrl(r.Url) == NormalizeUrl(repoUrl)) + .ToList(); + + if (matched.Count == 0) + { + logger.LogDebug("Webhook: no registered repo matches '{Url}' for tenant {Tenant}", repoUrl, tenantSlug); + return "No matching repository found."; + } + + int queued = 0; + + foreach (GitRepository repo in matched) + { + // If a webhook secret is configured for this repo, verify the signature. + string? webhookSecret = await vault.GetGitRepositorySecretValueAsync( + tenantId.Value, repo.Id, "WEBHOOK_SECRET", ct); + + if (webhookSecret is not null && hubSignature256 is not null) + { + if (!VerifyGitHubSignature(rawBody, hubSignature256, webhookSecret)) + { + logger.LogWarning("Webhook: signature mismatch for repo {Repo}", repo.Name); + continue; + } + } + + // Find all deployments that use this repo. + List deploymentIds = await db.AppDeployments + .Where(d => d.GitRepositoryId == repo.Id) + .Select(d => d.Id) + .ToListAsync(ct); + + foreach (Guid id in deploymentIds) + { + syncService.EnqueueSync(id); + queued++; + } + + logger.LogInformation( + "Webhook: queued {Count} sync(s) for repo '{Repo}'", deploymentIds.Count, repo.Name); + } + + return $"Queued {queued} sync(s)."; + } + + // ── Helpers ────────────────────────────────────────────────────────────────── + + private static string? ExtractRepoUrl(string rawBody) + { + try + { + using JsonDocument doc = JsonDocument.Parse(rawBody); + JsonElement root = doc.RootElement; + + // GitHub push event: repository.clone_url + if (root.TryGetProperty("repository", out JsonElement repoEl)) + { + if (repoEl.TryGetProperty("clone_url", out JsonElement cloneUrl)) + return cloneUrl.GetString(); + if (repoEl.TryGetProperty("ssh_url", out JsonElement sshUrl)) + return sshUrl.GetString(); + if (repoEl.TryGetProperty("html_url", out JsonElement htmlUrl)) + return htmlUrl.GetString(); + } + + // Azure DevOps push event: resource.repository.remoteUrl + if (root.TryGetProperty("resource", out JsonElement resourceEl)) + { + if (resourceEl.TryGetProperty("repository", out JsonElement azRepoEl) + && azRepoEl.TryGetProperty("remoteUrl", out JsonElement remoteUrl)) + return remoteUrl.GetString(); + } + + return null; + } + catch + { + return null; + } + } + + private static bool VerifyGitHubSignature(string payload, string signature, string secret) + { + // GitHub sends "sha256=" in X-Hub-Signature-256. + if (!signature.StartsWith("sha256=", StringComparison.Ordinal)) + return false; + + string receivedHex = signature["sha256=".Length..]; + + byte[] keyBytes = Encoding.UTF8.GetBytes(secret); + byte[] payloadBytes = Encoding.UTF8.GetBytes(payload); + + using HMACSHA256 hmac = new(keyBytes); + byte[] computed = hmac.ComputeHash(payloadBytes); + string computedHex = Convert.ToHexString(computed).ToLowerInvariant(); + + return CryptographicOperations.FixedTimeEquals( + Encoding.UTF8.GetBytes(computedHex), + Encoding.UTF8.GetBytes(receivedHex)); + } + + private static string NormalizeUrl(string url) => + url.TrimEnd('/').TrimEnd(".git".ToCharArray()).ToLowerInvariant(); +} diff --git a/src/EntKube.Web/Services/HarborService.cs b/src/EntKube.Web/Services/HarborService.cs new file mode 100644 index 0000000..5791d91 --- /dev/null +++ b/src/EntKube.Web/Services/HarborService.cs @@ -0,0 +1,759 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +// ── DTOs ────────────────────────────────────────────────────────────────────── + +public class DetectedHarbor +{ + public ClusterComponent Component { get; set; } = null!; + public HarborComponentConfig? Config { get; set; } + + public bool IsConfigured => Config is not null; + public Guid ComponentId => Component.Id; + public string DisplayName => Component.Name; + public string? ClusterDisplayName => Component.Cluster.Name; +} + +public class HarborProjectInfo +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public bool Public { get; set; } + public int RepoCount { get; set; } + public long StorageUsedBytes { get; set; } + public long StorageQuotaBytes { get; set; } + public DateTime CreatedAt { get; set; } +} + +public class HarborRepositoryInfo +{ + public long Id { get; set; } + public string Name { get; set; } = ""; + public int ArtifactCount { get; set; } + public long SizeBytes { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +public class HarborArtifactInfo +{ + public string Digest { get; set; } = ""; + public List Tags { get; set; } = []; + public long SizeBytes { get; set; } + public string? MediaType { get; set; } + public DateTime PushedAt { get; set; } +} + +public class HarborRobotInfo +{ + public long Id { get; set; } + public string Name { get; set; } = ""; + public string? Description { get; set; } + public bool Disabled { get; set; } + public long ExpiresAt { get; set; } + public DateTime CreatedAt { get; set; } +} + +public class HarborSystemInfo +{ + public string? HarborVersion { get; set; } + public string? RegistryUrl { get; set; } + public bool WithTrivy { get; set; } + public string? AuthMode { get; set; } +} + +/// +/// Manages Harbor container registry instances installed via the component catalog. +/// +/// Integration points: +/// - CNPG: injects database host/port/name/user into Helm values; stores DB password +/// as a component vault secret so InjectSecretsIntoValuesAsync delivers it at install time. +/// - S3: reads StorageLink credentials from the vault and stores them as component +/// vault secrets (harbor-s3-access-key, harbor-s3-secret-key) for injection at install time. +/// - Vault: admin password stored as component vault secret HARBOR_ADMIN_PASSWORD. +/// +public class HarborService( + IDbContextFactory dbFactory, + VaultService vaultService, + StorageService storageService, + IHttpClientFactory httpClientFactory) +{ + private static readonly JsonSerializerOptions _json = new(JsonSerializerDefaults.Web); + // ── Configuration ───────────────────────────────────────────────────────── + + /// + /// Creates or updates the Harbor configuration for a component. Stores the admin + /// password in vault and injects CNPG + S3 connection details into Helm values. + /// Pass null for adminPassword to leave the existing password unchanged. + /// + public async Task ConfigureAsync( + Guid tenantId, + Guid clusterComponentId, + Guid? cnpgDatabaseId, + Guid? storageLinkId, + string adminUsername, + string? adminPassword, + string? registryUrl, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent component = await db.ClusterComponents + .Include(c => c.Cluster) + .FirstOrDefaultAsync(c => c.Id == clusterComponentId && c.Cluster.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Component not found."); + + // Upsert the config record. + HarborComponentConfig? config = await db.HarborComponentConfigs + .FirstOrDefaultAsync(c => c.ClusterComponentId == clusterComponentId, ct); + + if (config is null) + { + config = new HarborComponentConfig + { + Id = Guid.NewGuid(), + TenantId = tenantId, + ClusterComponentId = clusterComponentId + }; + db.HarborComponentConfigs.Add(config); + } + + config.CnpgDatabaseId = cnpgDatabaseId; + config.StorageLinkId = storageLinkId; + config.AdminUsername = adminUsername; + + if (!string.IsNullOrWhiteSpace(registryUrl)) + { + config.RegistryUrl = registryUrl.TrimEnd('/'); + } + + await db.SaveChangesAsync(ct); + + // Store admin password in vault. + if (!string.IsNullOrWhiteSpace(adminPassword)) + { + await vaultService.InitializeVaultAsync(tenantId, ct); + string harborNs = component.Namespace ?? "harbor"; + string credSecretName = $"{component.ReleaseName ?? component.Name}-credentials"; + + VaultSecret adminPwSecret = await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "HARBOR_ADMIN_PASSWORD", adminPassword, ct); + await vaultService.ConfigureKubernetesSyncAsync( + adminPwSecret.Id, true, credSecretName, harborNs, ct); + } + + // Inject CNPG and S3 connection details into Helm values. + if (cnpgDatabaseId.HasValue) + { + await WriteDatabaseHelmValuesAsync(tenantId, clusterComponentId, cnpgDatabaseId.Value, ct); + } + + if (storageLinkId.HasValue) + { + await WriteStorageHelmValuesAsync(tenantId, clusterComponentId, storageLinkId.Value, ct); + } + + return config; + } + + /// + /// Injects CNPG database connection details into the component's Helm values. + /// Non-sensitive parts (host, port, db name, username) are written directly to HelmValues. + /// The database password is stored as a component vault secret so it is injected at + /// install time by InjectSecretsIntoValuesAsync without appearing in plain Helm YAML. + /// + public async Task WriteDatabaseHelmValuesAsync( + Guid tenantId, + Guid clusterComponentId, + Guid cnpgDatabaseId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent component = await db.ClusterComponents + .Include(c => c.Cluster) + .FirstOrDefaultAsync(c => c.Id == clusterComponentId && c.Cluster.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Component not found."); + + CnpgDatabase cnpgDb = await db.CnpgDatabases + .Include(d => d.CnpgCluster) + .FirstOrDefaultAsync(d => d.Id == cnpgDatabaseId && d.CnpgCluster.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("CNPG database not found."); + + string host = $"{cnpgDb.CnpgCluster.Name}-rw.{cnpgDb.CnpgCluster.Namespace}.svc.cluster.local"; + + component.HelmValues = YamlFormMerger.MergeFormValues( + component.HelmValues ?? "", + new Dictionary + { + ["database.type"] = "external", + ["database.external.host"] = host, + ["database.external.port"] = "5432", + ["database.external.username"] = cnpgDb.Owner, + ["database.external.coreDatabase"] = cnpgDb.Name, + ["database.external.sslmode"] = "disable" + }); + + await db.SaveChangesAsync(ct); + + // Store DB password as a component vault secret; injected at install time via the + // harbor-db-password hidden catalog field. + string? dbPassword = await vaultService.GetCnpgDatabasePasswordAsync(tenantId, cnpgDatabaseId, ct); + if (dbPassword is not null) + { + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "harbor-db-password", dbPassword, ct); + } + } + + /// + /// Injects S3-compatible storage configuration into the component's Helm values. + /// Non-sensitive parts (region, bucket, endpoint) are written directly to HelmValues. + /// Access key and secret key are stored as component vault secrets so they are injected + /// at install time by InjectSecretsIntoValuesAsync without appearing in plain Helm YAML. + /// + public async Task WriteStorageHelmValuesAsync( + Guid tenantId, + Guid clusterComponentId, + Guid storageLinkId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent component = await db.ClusterComponents + .Include(c => c.Cluster) + .FirstOrDefaultAsync(c => c.Id == clusterComponentId && c.Cluster.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Component not found."); + + StorageLink link = await db.StorageLinks + .FirstOrDefaultAsync(s => s.Id == storageLinkId && s.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Storage link not found."); + + string s3Endpoint = link.Endpoint ?? ""; + string region = link.Region ?? "us-east-1"; + string bucket = link.BucketName ?? ""; + + Dictionary s3Values = new() + { + ["persistence.imageChartStorage.type"] = "s3", + ["persistence.imageChartStorage.disableredirect"] = "true", + ["persistence.imageChartStorage.s3.region"] = region, + ["persistence.imageChartStorage.s3.bucket"] = bucket + }; + + if (!string.IsNullOrWhiteSpace(s3Endpoint)) + { + s3Values["persistence.imageChartStorage.s3.regionendpoint"] = s3Endpoint; + } + + component.HelmValues = YamlFormMerger.MergeFormValues(component.HelmValues ?? "", s3Values); + await db.SaveChangesAsync(ct); + + // Store S3 credentials as component vault secrets; injected at install time via hidden + // harbor-s3-access-key and harbor-s3-secret-key catalog fields. + await vaultService.InitializeVaultAsync(tenantId, ct); + + (string accessKey, string secretKey) = await storageService.GetStoredCredentialsInternalAsync(tenantId, storageLinkId, ct); + + if (!string.IsNullOrEmpty(accessKey)) + { + await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "harbor-s3-access-key", accessKey, ct); + } + + if (!string.IsNullOrEmpty(secretKey)) + { + await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "harbor-s3-secret-key", secretKey, ct); + } + } + + /// + /// Refreshes database and S3 credentials in Helm values from the stored config. + /// Call this before every install/upgrade to ensure the latest credentials are used. + /// + public async Task RefreshHelmValuesIfConfiguredAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + HarborComponentConfig? config = await GetConfigForComponentAsync(tenantId, clusterComponentId, ct); + + if (config is null) + { + return; + } + + if (config.CnpgDatabaseId.HasValue) + { + await WriteDatabaseHelmValuesAsync(tenantId, clusterComponentId, config.CnpgDatabaseId.Value, ct); + } + + if (config.StorageLinkId.HasValue) + { + await WriteStorageHelmValuesAsync(tenantId, clusterComponentId, config.StorageLinkId.Value, ct); + } + } + + // ── Queries ─────────────────────────────────────────────────────────────── + + public async Task GetConfigForComponentAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.HarborComponentConfigs + .FirstOrDefaultAsync(c => c.ClusterComponentId == clusterComponentId + && c.TenantId == tenantId, ct); + } + + public async Task> GetConfigsForClusterAsync( + Guid tenantId, Guid kubernetesClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.HarborComponentConfigs + .Where(c => c.TenantId == tenantId + && db.ClusterComponents + .Any(comp => comp.Id == c.ClusterComponentId && comp.ClusterId == kubernetesClusterId)) + .ToListAsync(ct); + } + + /// + /// Returns all CNPG databases available on the same cluster as the given component. + /// Used to populate the database selector in the Harbor config form. + /// + public async Task> GetDatabasesForClusterAsync( + Guid tenantId, Guid kubernetesClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.CnpgDatabases + .Include(d => d.CnpgCluster) + .Where(d => d.CnpgCluster.TenantId == tenantId + && d.CnpgCluster.KubernetesClusterId == kubernetesClusterId) + .OrderBy(d => d.CnpgCluster.Name) + .ThenBy(d => d.Name) + .ToListAsync(ct); + } + + // ── Discovery ───────────────────────────────────────────────────────────── + + /// + /// Returns all installed Harbor ClusterComponents across the tenant's clusters, + /// together with their configuration state. + /// + public async Task> GetDetectedHarborInstancesAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List components = await db.ClusterComponents + .Include(c => c.Cluster) + .Include(c => c.ExternalRoutes) + .Where(c => c.Cluster.TenantId == tenantId + && (c.Status == ComponentStatus.Installed || c.Status == ComponentStatus.Failed) + && (c.Name == "harbor" || c.HelmChartName == "harbor")) + .OrderBy(c => c.Cluster.Name).ThenBy(c => c.Name) + .ToListAsync(ct); + + List configs = await db.HarborComponentConfigs + .Where(c => c.TenantId == tenantId) + .ToListAsync(ct); + + return components.Select(comp => new DetectedHarbor + { + Component = comp, + Config = configs.FirstOrDefault(c => c.ClusterComponentId == comp.Id) + }).ToList(); + } + + // ── Harbor API helpers ──────────────────────────────────────────────────── + + private async Task GetAdminPasswordAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct) + { + return await vaultService.GetComponentSecretValueAsync( + tenantId, clusterComponentId, "HARBOR_ADMIN_PASSWORD", ct); + } + + private HttpClient BuildHarborClient(string registryUrl, string username, string password) + { + HttpClient http = httpClientFactory.CreateClient(); + http.BaseAddress = new Uri(registryUrl.TrimEnd('/')); + http.Timeout = TimeSpan.FromSeconds(20); + string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); + http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encoded); + http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + return http; + } + + private async Task GetHarborClientAsync( + Guid tenantId, HarborComponentConfig config, CancellationToken ct) + { + if (string.IsNullOrWhiteSpace(config.RegistryUrl)) + throw new InvalidOperationException("Harbor registry URL is not configured."); + + string? password = await GetAdminPasswordAsync(tenantId, config.ClusterComponentId, ct); + if (string.IsNullOrWhiteSpace(password)) + throw new InvalidOperationException("Harbor admin password is not stored in vault."); + + return BuildHarborClient(config.RegistryUrl, config.AdminUsername, password); + } + + // ── System Info ─────────────────────────────────────────────────────────── + + public async Task GetSystemInfoAsync( + Guid tenantId, HarborComponentConfig config, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.GetAsync("/api/v2.0/systeminfo", ct); + response.EnsureSuccessStatusCode(); + + JsonNode? json = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct)); + return new HarborSystemInfo + { + HarborVersion = json?["harbor_version"]?.GetValue(), + RegistryUrl = json?["registry_url"]?.GetValue(), + WithTrivy = json?["with_trivy"]?.GetValue() ?? false, + AuthMode = json?["auth_mode"]?.GetValue() + }; + } + + // ── Projects ────────────────────────────────────────────────────────────── + + public async Task> GetProjectsAsync( + Guid tenantId, HarborComponentConfig config, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.GetAsync("/api/v2.0/projects?page_size=100", ct); + response.EnsureSuccessStatusCode(); + + JsonArray? projects = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct))?.AsArray(); + if (projects is null) return []; + + return projects.Select(p => new HarborProjectInfo + { + Id = p?["id"]?.GetValue() ?? 0, + Name = p?["name"]?.GetValue() ?? "", + Public = p?["metadata"]?["public"]?.GetValue() == "true", + RepoCount = p?["repo_count"]?.GetValue() ?? 0, + StorageUsedBytes = p?["quota"]?["used"]?["storage"]?.GetValue() ?? 0, + StorageQuotaBytes = p?["quota"]?["hard"]?["storage"]?.GetValue() ?? -1, + CreatedAt = p?["creation_time"]?.GetValue() ?? DateTime.MinValue + }).OrderBy(p => p.Name).ToList(); + } + + public async Task CreateProjectAsync( + Guid tenantId, HarborComponentConfig config, + string name, bool isPublic, string? description = null, + CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + + JsonObject body = new() + { + ["project_name"] = name, + ["metadata"] = new JsonObject { ["public"] = isPublic ? "true" : "false" } + }; + if (!string.IsNullOrWhiteSpace(description)) + body["description"] = description; + + HttpResponseMessage response = await http.PostAsync( + "/api/v2.0/projects", + new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"), + ct); + + if (!response.IsSuccessStatusCode) + { + string detail = await response.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException($"Harbor API error ({response.StatusCode}): {detail}"); + } + } + + public async Task DeleteProjectAsync( + Guid tenantId, HarborComponentConfig config, string projectName, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.DeleteAsync($"/api/v2.0/projects/{projectName}", ct); + + if (!response.IsSuccessStatusCode) + { + string detail = await response.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException($"Harbor API error ({response.StatusCode}): {detail}"); + } + } + + // ── Repositories & Artifacts ───────────────────────────────────────────── + + public async Task> GetRepositoriesAsync( + Guid tenantId, HarborComponentConfig config, string projectName, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.GetAsync( + $"/api/v2.0/projects/{projectName}/repositories?page_size=100", ct); + response.EnsureSuccessStatusCode(); + + JsonArray? repos = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct))?.AsArray(); + if (repos is null) return []; + + return repos.Select(r => new HarborRepositoryInfo + { + Id = r?["id"]?.GetValue() ?? 0, + Name = r?["name"]?.GetValue() ?? "", + ArtifactCount = r?["artifact_count"]?.GetValue() ?? 0, + SizeBytes = r?["size"]?.GetValue() ?? 0, + UpdatedAt = r?["update_time"]?.GetValue() + }).OrderBy(r => r.Name).ToList(); + } + + public async Task> GetArtifactsAsync( + Guid tenantId, HarborComponentConfig config, + string projectName, string repositoryName, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + string repoEncoded = Uri.EscapeDataString(repositoryName); + HttpResponseMessage response = await http.GetAsync( + $"/api/v2.0/projects/{projectName}/repositories/{repoEncoded}/artifacts?with_tag=true&page_size=50", ct); + response.EnsureSuccessStatusCode(); + + JsonArray? artifacts = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct))?.AsArray(); + if (artifacts is null) return []; + + return artifacts.Select(a => + { + List tags = a?["tags"]?.AsArray() + .Select(t => t?["name"]?.GetValue() ?? "") + .Where(t => t != "") + .ToList() ?? []; + + return new HarborArtifactInfo + { + Digest = a?["digest"]?.GetValue() ?? "", + Tags = tags, + SizeBytes = a?["size"]?.GetValue() ?? 0, + MediaType = a?["media_type"]?.GetValue(), + PushedAt = a?["push_time"]?.GetValue() ?? DateTime.MinValue + }; + }).OrderByDescending(a => a.PushedAt).ToList(); + } + + // ── Robot Accounts ──────────────────────────────────────────────────────── + + public async Task> GetRobotsAsync( + Guid tenantId, HarborComponentConfig config, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.GetAsync("/api/v2.0/robots?page_size=100", ct); + response.EnsureSuccessStatusCode(); + + JsonArray? robots = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct))?.AsArray(); + if (robots is null) return []; + + return robots.Select(r => new HarborRobotInfo + { + Id = r?["id"]?.GetValue() ?? 0, + Name = r?["name"]?.GetValue() ?? "", + Description = r?["description"]?.GetValue(), + Disabled = r?["disable"]?.GetValue() ?? false, + ExpiresAt = r?["expires_at"]?.GetValue() ?? -1, + CreatedAt = r?["creation_time"]?.GetValue() ?? DateTime.MinValue + }).OrderBy(r => r.Name).ToList(); + } + + /// + /// Creates a robot account scoped to one or more projects (or system-wide). + /// Pass null for projectNames to create a system-level robot with push/pull on all projects. + /// Returns the generated secret (only available at creation time). + /// The secret is also stored in vault as "harbor-robot-{name}" for reference. + /// + public async Task CreateRobotAsync( + Guid tenantId, HarborComponentConfig config, + string name, string? description, + IEnumerable? projectNames, + bool canPush, bool canPull, + long durationDays = -1, + CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + + JsonArray permissions; + if (projectNames is not null) + { + List projects = projectNames.ToList(); + permissions = new JsonArray(projects.Select(proj => + { + JsonArray access = []; + if (canPull) + { + access.Add(new JsonObject { ["resource"] = "repository", ["action"] = "pull" }); + access.Add(new JsonObject { ["resource"] = "artifact", ["action"] = "read" }); + } + if (canPush) + { + access.Add(new JsonObject { ["resource"] = "repository", ["action"] = "push" }); + access.Add(new JsonObject { ["resource"] = "artifact", ["action"] = "delete" }); + access.Add(new JsonObject { ["resource"] = "tag", ["action"] = "create" }); + access.Add(new JsonObject { ["resource"] = "tag", ["action"] = "delete" }); + } + return (JsonNode)new JsonObject + { + ["kind"] = "project", + ["namespace"] = proj, + ["access"] = access + }; + }).ToArray()); + } + else + { + JsonArray sysAccess = []; + if (canPull) + { + sysAccess.Add(new JsonObject { ["resource"] = "repository", ["action"] = "pull" }); + sysAccess.Add(new JsonObject { ["resource"] = "artifact", ["action"] = "read" }); + } + if (canPush) + { + sysAccess.Add(new JsonObject { ["resource"] = "repository", ["action"] = "push" }); + sysAccess.Add(new JsonObject { ["resource"] = "artifact", ["action"] = "delete" }); + sysAccess.Add(new JsonObject { ["resource"] = "tag", ["action"] = "create" }); + sysAccess.Add(new JsonObject { ["resource"] = "tag", ["action"] = "delete" }); + } + permissions = [new JsonObject + { + ["kind"] = "system", + ["namespace"] = "/", + ["access"] = sysAccess + }]; + } + + JsonObject body = new() + { + ["name"] = name, + ["level"] = projectNames is not null ? "project" : "system", + ["duration"] = durationDays, + ["permissions"] = permissions + }; + if (!string.IsNullOrWhiteSpace(description)) + body["description"] = description; + + HttpResponseMessage response = await http.PostAsync( + "/api/v2.0/robots", + new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"), + ct); + + if (!response.IsSuccessStatusCode) + { + string detail = await response.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException($"Harbor API error ({response.StatusCode}): {detail}"); + } + + JsonNode? result = JsonNode.Parse(await response.Content.ReadAsStringAsync(ct)); + string secret = result?["secret"]?.GetValue() + ?? throw new InvalidOperationException("Harbor did not return a robot secret."); + + // Store the secret in vault so it can be retrieved later if needed. + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetComponentSecretAsync( + tenantId, config.ClusterComponentId, $"harbor-robot-{name}", secret, ct); + + return secret; + } + + public async Task DeleteRobotAsync( + Guid tenantId, HarborComponentConfig config, long robotId, CancellationToken ct = default) + { + using HttpClient http = await GetHarborClientAsync(tenantId, config, ct); + HttpResponseMessage response = await http.DeleteAsync($"/api/v2.0/robots/{robotId}", ct); + + if (!response.IsSuccessStatusCode) + { + string detail = await response.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException($"Harbor API error ({response.StatusCode}): {detail}"); + } + } + + // ── Project–App linking ─────────────────────────────────────────────────── + + /// + /// Returns all HarborProject tracking records for a given Harbor config, + /// including the linked App and its Customer. + /// + public async Task> GetTrackedProjectsAsync( + Guid tenantId, Guid harborComponentConfigId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.HarborProjects + .Include(p => p.LinkedApp).ThenInclude(a => a!.Customer) + .Where(p => p.TenantId == tenantId && p.HarborComponentConfigId == harborComponentConfigId) + .OrderBy(p => p.ProjectName) + .ToListAsync(ct); + } + + /// + /// Links a Harbor project to a customer app so the customer can manage + /// the project through the portal. Creates the tracking record if it + /// does not exist yet. + /// + public async Task LinkProjectToAppAsync( + Guid tenantId, Guid harborComponentConfigId, + string projectName, Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + HarborProject? existing = await db.HarborProjects + .FirstOrDefaultAsync(p => p.HarborComponentConfigId == harborComponentConfigId + && p.ProjectName == projectName, ct); + + if (existing is null) + { + existing = new HarborProject + { + Id = Guid.NewGuid(), + TenantId = tenantId, + HarborComponentConfigId = harborComponentConfigId, + ProjectName = projectName + }; + db.HarborProjects.Add(existing); + } + + existing.LinkedAppId = appId; + await db.SaveChangesAsync(ct); + return existing; + } + + /// Removes the app link from a tracked Harbor project. + public async Task UnlinkProjectFromAppAsync( + Guid tenantId, Guid projectId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + HarborProject? project = await db.HarborProjects + .FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId, ct); + + if (project is null) return; + + project.LinkedAppId = null; + await db.SaveChangesAsync(ct); + } + + /// + /// Returns the Harbor project (with its config) linked to a specific customer app, + /// or null if the app has no linked Harbor project. Used by the customer portal. + /// + public async Task GetProjectForAppAsync( + Guid tenantId, Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.HarborProjects + .Include(p => p.HarborComponentConfig) + .Include(p => p.LinkedApp).ThenInclude(a => a!.Customer) + .FirstOrDefaultAsync(p => p.TenantId == tenantId && p.LinkedAppId == appId, ct); + } +} diff --git a/src/EntKube.Web/Services/IKubernetesClientFactory.cs b/src/EntKube.Web/Services/IKubernetesClientFactory.cs index 9227062..a42a181 100644 --- a/src/EntKube.Web/Services/IKubernetesClientFactory.cs +++ b/src/EntKube.Web/Services/IKubernetesClientFactory.cs @@ -22,6 +22,12 @@ public interface IKubernetesClientFactory /// Task DeleteManifestAsync(string kind, string name, string ns, string kubeconfig, CancellationToken ct = default); + /// + /// Applies a JSON merge patch to an existing resource. Only the specified fields are changed; + /// all other fields are preserved. Safer than ApplyManifestAsync for partial updates. + /// + Task PatchJsonAsync(string resource, string name, string ns, string jsonPatch, string kubeconfig, CancellationToken ct = default); + /// /// Ensures a namespace exists, creating it if it doesn't. /// Equivalent to "kubectl create namespace {ns} --dry-run=client -o yaml | kubectl apply -f -". @@ -47,10 +53,58 @@ public interface IKubernetesClientFactory Task ExecuteMongoAsync(string clusterName, string ns, string script, string kubeconfig, string? username = null, string? password = null, CancellationToken ct = default); + /// Same as ExecuteMongoAsync but returns stdout from mongosh. + Task ExecuteMongoWithOutputAsync(string clusterName, string ns, string script, string kubeconfig, + string? username = null, string? password = null, CancellationToken ct = default); + /// /// Reads a single key from a Kubernetes Secret and returns the decoded value. /// Returns null if the secret or key does not exist. /// Task GetSecretValueAsync(string secretName, string key, string ns, string kubeconfig, CancellationToken ct = default); + + /// + /// Executes SQL on an arbitrary Postgres pod via kubectl exec + psql. + /// The admin password is passed via the PGPASSWORD environment variable so it + /// never appears in the process argument list. + /// + Task ExecuteSqlOnPodAsync(string podName, string ns, string sql, string kubeconfig, + string username = "postgres", string? password = null, CancellationToken ct = default); + + /// + /// Same as ExecuteSqlOnPodAsync but returns the psql stdout output. + /// Used for queries that need to read results (e.g. listing databases). + /// + Task ExecuteSqlOnPodWithOutputAsync(string podName, string ns, string sql, string kubeconfig, + string username = "postgres", string? password = null, CancellationToken ct = default); + + /// + /// Runs an arbitrary command on a pod (no stdin) and returns stdout. + /// Used for pg_dump, which writes the dump to stdout. + /// Environment variables (e.g. PGPASSWORD) are set via ArgumentList so + /// special characters in values are never misinterpreted. + /// + Task RunCommandOnPodAsync(string podName, string ns, IReadOnlyList command, + string kubeconfig, IReadOnlyDictionary? envVars = null, + CancellationToken ct = default); + + /// + /// Executes SQL directly connected to a specific database on a CNPG primary pod. + /// Unlike ExecuteSqlAsync (which connects to the default 'postgres' database), + /// this opens psql with -d {database} so the SQL runs in the correct database + /// context from the start — avoiding \c reconnection which can silently fail. + /// + Task ExecuteSqlInCnpgDatabaseAsync(string clusterName, string ns, string database, + string sql, string kubeconfig, CancellationToken ct = default); + + Task ExecuteSqlInCnpgDatabaseWithOutputAsync(string clusterName, string ns, string database, + string sql, string kubeconfig, CancellationToken ct = default); + + /// + /// Runs a command on a pod with content piped via stdin, returning stdout. + /// Used for commands that consume stdin (e.g. rabbitmqctl import_definitions -). + /// + Task RunCommandOnPodWithStdinAsync(string podName, string ns, IReadOnlyList command, + string stdin, string kubeconfig, CancellationToken ct = default); } diff --git a/src/EntKube.Web/Services/IncidentService.cs b/src/EntKube.Web/Services/IncidentService.cs new file mode 100644 index 0000000..8fe0b95 --- /dev/null +++ b/src/EntKube.Web/Services/IncidentService.cs @@ -0,0 +1,357 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using EntKube.Web.Data; + +namespace EntKube.Web.Services; + +public class IncidentService(IDbContextFactory dbFactory) +{ + /// + /// Returns incidents that are relevant to a specific set of deployments, + /// matched by cluster ID and namespace label in the alert's LabelsJson. + /// Used by the customer portal to show incidents scoped to the customer's apps. + /// + public async Task> GetIncidentsForDeploymentsAsync( + List deployments, + int windowDays = 30, + IncidentStatus? status = null, + CancellationToken ct = default) + { + if (deployments.Count == 0) return []; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime from = DateTime.UtcNow.AddDays(-windowDays); + + HashSet clusterIds = deployments.Select(d => d.ClusterId).ToHashSet(); + HashSet namespaces = deployments.Select(d => d.Namespace).ToHashSet(); + + IQueryable query = db.AlertIncidents + .Include(i => i.Cluster) + .Include(i => i.Notes.OrderBy(n => n.CreatedAt)) + .Where(i => clusterIds.Contains(i.ClusterId) && i.StartsAt >= from) + .OrderByDescending(i => i.StartsAt); + + if (status.HasValue) + query = query.Where(i => i.Status == status.Value); + + List all = await query.ToListAsync(ct); + + // Filter client-side by namespace label (JSON parsing can't run in EF) + return all.Where(i => + { + try + { + using JsonDocument doc = JsonDocument.Parse(i.LabelsJson); + return !doc.RootElement.TryGetProperty("namespace", out JsonElement ns) + || namespaces.Contains(ns.GetString() ?? ""); + } + catch { return true; } + }).ToList(); + } + + public async Task> GetUpcomingMaintenanceAsync( + Guid tenantId, HashSet? clusterIds = null, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime now = DateTime.UtcNow; + DateTime lookahead = now.AddDays(7); + + List windows = await db.MaintenanceWindows + .Include(w => w.Cluster) + .Where(w => w.TenantId == tenantId + && w.EndsAt >= now + && w.StartsAt <= lookahead) + .OrderBy(w => w.StartsAt) + .ToListAsync(ct); + + if (clusterIds is not null && clusterIds.Count > 0) + windows = windows.Where(w => w.ClusterId == null || clusterIds.Contains(w.ClusterId.Value)).ToList(); + + return windows; + } + + + public async Task> GetIncidentsForTenantAsync( + Guid tenantId, + IncidentStatus? status = null, + string? severity = null, + Guid? clusterId = null, + DateTime? from = null, + DateTime? to = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + IQueryable query = db.AlertIncidents + .Include(i => i.Cluster) + .Include(i => i.Notes.OrderBy(n => n.CreatedAt)) + .Where(i => i.Cluster.TenantId == tenantId) + .OrderByDescending(i => i.StartsAt); + + if (status.HasValue) + query = query.Where(i => i.Status == status.Value); + if (!string.IsNullOrEmpty(severity)) + query = query.Where(i => i.Severity == severity); + if (clusterId.HasValue) + query = query.Where(i => i.ClusterId == clusterId.Value); + if (from.HasValue) + query = query.Where(i => i.StartsAt >= from.Value); + if (to.HasValue) + query = query.Where(i => i.StartsAt <= to.Value); + + return await query.ToListAsync(ct); + } + + public async Task GetByIdAsync(Guid id, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AlertIncidents + .Include(i => i.Cluster) + .Include(i => i.Notes.OrderBy(n => n.CreatedAt)) + .FirstOrDefaultAsync(i => i.Id == id, ct); + } + + public async Task AcknowledgeAsync(Guid id, string acknowledgedBy, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AlertIncident? incident = await db.AlertIncidents.FindAsync([id], ct); + if (incident is null || incident.Status != IncidentStatus.Active) return; + + incident.Status = IncidentStatus.Acknowledged; + incident.AcknowledgedBy = acknowledgedBy; + incident.AcknowledgedAt = DateTime.UtcNow; + incident.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + } + + public async Task ResolveAsync(Guid id, string resolvedBy, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + AlertIncident? incident = await db.AlertIncidents.FindAsync([id], ct); + if (incident is null || incident.Status == IncidentStatus.Resolved) return; + + incident.Status = IncidentStatus.Resolved; + incident.ResolvedAt = DateTime.UtcNow; + incident.EndsAt ??= DateTime.UtcNow; + incident.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + } + + public async Task AddNoteAsync(Guid incidentId, string author, string content, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + db.IncidentNotes.Add(new IncidentNote + { + IncidentId = incidentId, + Author = author, + Content = content + }); + await db.SaveChangesAsync(ct); + } + + public async Task GetUptimeAsync( + Guid deploymentId, + int windowDays, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime from = DateTime.UtcNow.AddDays(-windowDays); + + List snapshots = await db.DeploymentHealthSnapshots + .Where(s => s.DeploymentId == deploymentId && s.SnapshotAt >= from) + .OrderBy(s => s.SnapshotAt) + .ToListAsync(ct); + + if (snapshots.Count == 0) + return new UptimeResult(windowDays, null); + + int healthy = snapshots.Count(s => s.HealthStatus == HealthStatus.Healthy); + double percent = (double)healthy / snapshots.Count * 100.0; + return new UptimeResult(windowDays, Math.Round(percent, 2)); + } + + public async Task GetActiveAlertCountForTenantAsync(Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AlertIncidents + .CountAsync(i => i.Cluster.TenantId == tenantId + && (i.Status == IncidentStatus.Active || i.Status == IncidentStatus.Acknowledged), ct); + } + + public async Task GetStatsForTenantAsync( + Guid tenantId, int windowDays = 30, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime from = DateTime.UtcNow.AddDays(-windowDays); + + List incidents = await db.AlertIncidents + .Where(i => i.Cluster.TenantId == tenantId && i.StartsAt >= from) + .ToListAsync(ct); + + int total = incidents.Count; + int active = incidents.Count(i => i.Status == IncidentStatus.Active); + int acked = incidents.Count(i => i.Status == IncidentStatus.Acknowledged); + int resolved = incidents.Count(i => i.Status == IncidentStatus.Resolved); + int critical = incidents.Count(i => i.Severity == "critical"); + int warning = incidents.Count(i => i.Severity == "warning"); + + List resolvedWithTime = incidents + .Where(i => i.Status == IncidentStatus.Resolved && i.ResolvedAt.HasValue) + .ToList(); + + double? mttrMinutes = resolvedWithTime.Count > 0 + ? resolvedWithTime.Average(i => (i.ResolvedAt!.Value - i.StartsAt).TotalMinutes) + : null; + + List ackedWithTime = incidents + .Where(i => i.AcknowledgedAt.HasValue) + .ToList(); + + double? mttaMinutes = ackedWithTime.Count > 0 + ? ackedWithTime.Average(i => (i.AcknowledgedAt!.Value - i.StartsAt).TotalMinutes) + : null; + + // Daily buckets for sparkline + List daily = incidents + .GroupBy(i => i.StartsAt.Date) + .Select(g => new DailyAlertCount(g.Key, g.Count())) + .OrderBy(d => d.Date) + .ToList(); + + return new AlertStats( + WindowDays: windowDays, + Total: total, + Active: active, + Acknowledged: acked, + Resolved: resolved, + Critical: critical, + Warning: warning, + MttrMinutes: mttrMinutes, + MttaMinutes: mttaMinutes, + Daily: daily); + } + + public async Task IsInMaintenanceAsync(Guid tenantId, Guid? clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + DateTime now = DateTime.UtcNow; + return await db.MaintenanceWindows.AnyAsync(w => + w.TenantId == tenantId + && w.StartsAt <= now + && w.EndsAt >= now + && (w.ClusterId == null || w.ClusterId == clusterId), ct); + } + + public async Task> GetMaintenanceWindowsAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.MaintenanceWindows + .Include(w => w.Cluster) + .Where(w => w.TenantId == tenantId) + .OrderByDescending(w => w.StartsAt) + .ToListAsync(ct); + } + + public async Task CreateMaintenanceWindowAsync( + Guid tenantId, string title, string? description, Guid? clusterId, + DateTime startsAt, DateTime endsAt, string createdBy, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + db.MaintenanceWindows.Add(new MaintenanceWindow + { + TenantId = tenantId, + ClusterId = clusterId, + Title = title, + Description = description, + StartsAt = startsAt, + EndsAt = endsAt, + CreatedBy = createdBy + }); + await db.SaveChangesAsync(ct); + } + + public async Task DeleteMaintenanceWindowAsync(Guid id, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + MaintenanceWindow? w = await db.MaintenanceWindows.FindAsync([id], ct); + if (w is not null) db.MaintenanceWindows.Remove(w); + await db.SaveChangesAsync(ct); + } + + public async Task GetSlaTargetAsync( + Guid tenantId, Guid? customerId, Guid? appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + // Most specific target wins: app > customer > tenant + return await db.SlaTargets + .Where(s => s.TenantId == tenantId + && (appId == null || s.AppId == appId || s.AppId == null) + && (customerId == null || s.CustomerId == customerId || s.CustomerId == null)) + .OrderByDescending(s => s.AppId.HasValue) + .ThenByDescending(s => s.CustomerId.HasValue) + .FirstOrDefaultAsync(ct); + } + + public async Task UpsertSlaTargetAsync( + Guid tenantId, Guid? customerId, Guid? appId, + double targetPercent, int windowDays, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + SlaTarget? existing = await db.SlaTargets + .FirstOrDefaultAsync(s => s.TenantId == tenantId + && s.CustomerId == customerId && s.AppId == appId, ct); + + if (existing is null) + { + db.SlaTargets.Add(new SlaTarget + { + TenantId = tenantId, + CustomerId = customerId, + AppId = appId, + TargetPercent = targetPercent, + MeasurementWindowDays = windowDays + }); + } + else + { + existing.TargetPercent = targetPercent; + existing.MeasurementWindowDays = windowDays; + } + await db.SaveChangesAsync(ct); + } +} + +public record UptimeResult(int WindowDays, double? Percent) +{ + public string Display => Percent.HasValue ? $"{Percent:F2}%" : "No data"; +} + +public record AlertStats( + int WindowDays, + int Total, + int Active, + int Acknowledged, + int Resolved, + int Critical, + int Warning, + double? MttrMinutes, + double? MttaMinutes, + List Daily) +{ + public string FormatMttr() => MttrMinutes.HasValue + ? MttrMinutes.Value >= 60 + ? $"{MttrMinutes.Value / 60:F1}h" + : $"{(int)MttrMinutes.Value}m" + : "—"; + + public string FormatMtta() => MttaMinutes.HasValue + ? MttaMinutes.Value >= 60 + ? $"{MttaMinutes.Value / 60:F1}h" + : $"{(int)MttaMinutes.Value}m" + : "—"; +} + +public record DailyAlertCount(DateTime Date, int Count); diff --git a/src/EntKube.Web/Services/KeycloakService.cs b/src/EntKube.Web/Services/KeycloakService.cs index e609dc3..425624c 100644 --- a/src/EntKube.Web/Services/KeycloakService.cs +++ b/src/EntKube.Web/Services/KeycloakService.cs @@ -1,6 +1,7 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; using Amazon.S3; using Amazon.S3.Model; using EntKube.Web.Data; @@ -56,19 +57,39 @@ public class KeycloakRealmDetails public List Themes { get; set; } = []; } +public class CopyResult +{ + public int Copied { get; set; } + public int Skipped { get; set; } + public List Errors { get; set; } = []; +} + /// -/// A detected Keycloak component on a cluster, with its configuration status. +/// A Keycloak instance tracked by EntKube. May be an EntKube-managed install +/// (Component is set) or an externally deployed instance (Component is null). /// public class DetectedKeycloak { - public required ClusterComponent Component { get; set; } + /// Set for EntKube-managed installs; null for external instances. + public ClusterComponent? Component { get; set; } public KeycloakComponentConfig? Config { get; set; } /// External URL from the component's ExternalRoutes, if any. public string? SuggestedAdminUrl { get; set; } public bool IsConfigured => Config is not null; + public bool IsExternal => Component is null; public bool HasDatabase => Config?.CnpgDatabaseId is not null; + + /// + /// Stable ID used as the vault partition key for secrets and theme CSS. + /// For managed instances this is the ClusterComponent ID; for external instances + /// the KeycloakComponentConfig ID is used instead. + /// + public Guid ComponentId => Component?.Id ?? Config!.Id; + + public string DisplayName => Component?.Name ?? Config?.DisplayName ?? "External Keycloak"; + public string? ClusterDisplayName => Component?.Cluster.Name; } /// @@ -84,7 +105,8 @@ public class DetectedKeycloak public class KeycloakService( IDbContextFactory dbFactory, VaultService vaultService, - IHttpClientFactory httpClientFactory) + IHttpClientFactory httpClientFactory, + CnpgService cnpgService) { private readonly record struct TokenCacheKey(Guid ComponentId); private readonly Dictionary _tokenCache = []; @@ -114,16 +136,24 @@ public class KeycloakService( List configs = await db.KeycloakComponentConfigs .Include(cfg => cfg.CnpgDatabase).ThenInclude(d => d!.CnpgCluster) + .Include(cfg => cfg.RegisteredPostgresDatabase) + .ThenInclude(d => d!.RegisteredPostgresInstance) .Where(cfg => cfg.TenantId == tenantId) .ToListAsync(ct); - return components.Select(comp => + List result = components.Select(comp => { KeycloakComponentConfig? cfg = configs.FirstOrDefault(c => c.ClusterComponentId == comp.Id); + // Append the http.relativePath (e.g. "/auth") from Helm values to the suggested URL + // so the pre-filled admin URL is correct when the user configures via the Identity tab. + string relativePath = YamlFormMerger.ExtractValue(comp.HelmValues ?? "", "http.relativePath") ?? "/auth"; + if (relativePath != "/" && !relativePath.StartsWith('/')) + relativePath = "/" + relativePath; + string? suggestedUrl = comp.ExternalRoutes .OrderBy(r => r.CreatedAt) - .Select(r => $"https://{r.Hostname}") + .Select(r => $"https://{r.Hostname}{(relativePath == "/" ? "" : relativePath)}") .FirstOrDefault(); return new DetectedKeycloak @@ -133,6 +163,19 @@ public class KeycloakService( SuggestedAdminUrl = suggestedUrl ?? cfg?.AdminUrl }; }).ToList(); + + // Also surface externally deployed Keycloaks (registered without a ClusterComponent). + foreach (KeycloakComponentConfig cfg in configs.Where(c => c.ClusterComponentId is null)) + { + result.Add(new DetectedKeycloak + { + Component = null, + Config = cfg, + SuggestedAdminUrl = cfg.AdminUrl + }); + } + + return result; } // ── Configuration ───────────────────────────────────────────────────────── @@ -149,6 +192,7 @@ public class KeycloakService( string adminUsername, string adminPassword, string adminUrl, + Guid? registeredPostgresDatabaseId = null, CancellationToken ct = default) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -169,6 +213,16 @@ public class KeycloakService( ?? throw new InvalidOperationException("CNPG database not found."); } + RegisteredPostgresDatabase? regPgDb = null; + if (registeredPostgresDatabaseId.HasValue) + { + regPgDb = await db.RegisteredPostgresDatabases + .Include(d => d.RegisteredPostgresInstance) + .FirstOrDefaultAsync(d => d.Id == registeredPostgresDatabaseId + && d.RegisteredPostgresInstance.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres database not found."); + } + // Upsert the config record. KeycloakComponentConfig? config = await db.KeycloakComponentConfigs @@ -186,6 +240,7 @@ public class KeycloakService( } config.CnpgDatabaseId = cnpgDatabaseId; + config.RegisteredPostgresDatabaseId = registeredPostgresDatabaseId; config.AdminUsername = adminUsername; config.AdminUrl = adminUrl.TrimEnd('/'); @@ -220,6 +275,20 @@ public class KeycloakService( ["database.username"] = cnpgDb.Owner }); } + else if (regPgDb is not null) + { + RegisteredPostgresInstance rpi = regPgDb.RegisteredPostgresInstance; + string dbHost = $"{rpi.ServiceName}.{rpi.Namespace}.svc.cluster.local"; + component.HelmValues = YamlFormMerger.MergeFormValues( + component.HelmValues ?? "", + new Dictionary + { + ["database.hostname"] = dbHost, + ["database.port"] = rpi.Port.ToString(), + ["database.database"] = regPgDb.Name, + ["database.username"] = regPgDb.Owner + }); + } await db.SaveChangesAsync(ct); @@ -268,6 +337,33 @@ public class KeycloakService( dbPwSecret.Id, true, credSecretName, keycloakNs, ct); } } + else if (regPgDb is not null) + { + RegisteredPostgresInstance rpi = regPgDb.RegisteredPostgresInstance; + string host = $"{rpi.ServiceName}.{rpi.Namespace}.svc.cluster.local"; + string jdbcUrl = $"jdbc:postgresql://{host}:{rpi.Port}/{regPgDb.Name}"; + + VaultSecret dbUrlSecret = await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "KC_DB_URL", jdbcUrl, ct); + await vaultService.ConfigureKubernetesSyncAsync( + dbUrlSecret.Id, true, credSecretName, keycloakNs, ct); + + VaultSecret dbUserSecret = await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "KC_DB_USERNAME", regPgDb.Owner, ct); + await vaultService.ConfigureKubernetesSyncAsync( + dbUserSecret.Id, true, credSecretName, keycloakNs, ct); + + string? dbPassword = await vaultService.GetRegisteredPostgresDatabasePasswordAsync( + tenantId, registeredPostgresDatabaseId!.Value, ct); + + if (dbPassword is not null) + { + VaultSecret dbPwSecret = await vaultService.SetComponentSecretAsync( + tenantId, clusterComponentId, "KC_DB_PASSWORD", dbPassword, ct); + await vaultService.ConfigureKubernetesSyncAsync( + dbPwSecret.Id, true, credSecretName, keycloakNs, ct); + } + } return config; } @@ -294,6 +390,76 @@ public class KeycloakService( await WriteDatabaseHelmValuesAsync(tenantId, clusterComponentId, config.CnpgDatabaseId.Value, ct); } + /// + /// Grants full schema ownership to the database owner before every Keycloak install + /// or upgrade. Safe no-op when no CNPG database is configured. + /// + /// This is idempotent in PostgreSQL — granting privileges that already exist does + /// nothing. It fixes "permission denied for table databasechangelog" errors that + /// occur when a database dump from another Keycloak instance is restored into the + /// CNPG database, leaving Liquibase tables owned by the original user. + /// + public async Task GrantDatabaseOwnerPermissionsIfConfiguredAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakComponentConfig? config = await db.KeycloakComponentConfigs + .Include(c => c.CnpgDatabase) + .FirstOrDefaultAsync(c => c.ClusterComponentId == clusterComponentId && c.TenantId == tenantId, ct); + + if (config?.CnpgDatabase is null) return; + + await cnpgService.GrantDatabaseOwnerPermissionsAsync( + tenantId, config.CnpgDatabase.CnpgClusterId, config.CnpgDatabase.Id, ct); + } + + /// + /// Releases a stuck Liquibase changelog lock before every Keycloak install or upgrade. + /// Safe no-op when no CNPG database is configured or when no lock row exists. + /// + /// Liquibase can leave the lock set to true if Keycloak crashes mid-migration. A locked + /// changelog causes Keycloak to hang at startup (pod Running, empty 500 responses) because + /// it waits indefinitely to acquire the lock. + /// + public async Task ReleaseLiquibaseLockIfConfiguredAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakComponentConfig? config = await db.KeycloakComponentConfigs + .Include(c => c.CnpgDatabase) + .FirstOrDefaultAsync(c => c.ClusterComponentId == clusterComponentId && c.TenantId == tenantId, ct); + + if (config?.CnpgDatabase is null) return; + + await cnpgService.ReleaseLiquibaseLockAsync( + tenantId, config.CnpgDatabase.CnpgClusterId, config.CnpgDatabase.Id, ct); + } + + /// + /// Updates the frontendUrl realm attribute for all realms in the Keycloak database + /// to match the admin URL configured in EntKube. Safe to run on fresh installs (no-op if + /// no frontendUrl rows exist). Use after restoring a database from another instance whose + /// URL differs from the current deployment. + /// + public async Task FixRealmUrlsIfConfiguredAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakComponentConfig? config = await db.KeycloakComponentConfigs + .Include(c => c.CnpgDatabase) + .FirstOrDefaultAsync(c => c.ClusterComponentId == clusterComponentId && c.TenantId == tenantId, ct); + + if (config?.CnpgDatabase is null || string.IsNullOrWhiteSpace(config.AdminUrl)) return; + + string url = config.AdminUrl.TrimEnd('/'); + + await cnpgService.FixKeycloakRealmFrontendUrlAsync( + tenantId, config.CnpgDatabase.CnpgClusterId, config.CnpgDatabase.Id, url, ct); + } + /// /// Writes CNPG database connection details (non-sensitive: hostname, port, database name, /// username) into the component's HelmValues so the keycloakx chart always has a working @@ -384,11 +550,12 @@ public class KeycloakService( if (!string.IsNullOrWhiteSpace(newAdminPassword)) { + Guid vaultId = config.ClusterComponentId ?? config.Id; await vaultService.SetComponentSecretAsync( - tenantId, config.ClusterComponentId, "KEYCLOAK_ADMIN_PASSWORD", newAdminPassword, ct); + tenantId, vaultId, "KEYCLOAK_ADMIN_PASSWORD", newAdminPassword, ct); } - _tokenCache.Remove(new TokenCacheKey(config.ClusterComponentId)); + _tokenCache.Remove(new TokenCacheKey(config.ClusterComponentId ?? config.Id)); } /// @@ -405,7 +572,42 @@ public class KeycloakService( db.KeycloakComponentConfigs.Remove(config); await db.SaveChangesAsync(ct); - _tokenCache.Remove(new TokenCacheKey(config.ClusterComponentId)); + _tokenCache.Remove(new TokenCacheKey(config.ClusterComponentId ?? config.Id)); + } + + /// + /// Registers an externally deployed Keycloak (not installed via EntKube) so + /// it appears in the Identity tab. Credentials are stored in vault keyed by + /// the new config's own Id (no ClusterComponent is involved). + /// + public async Task RegisterExternalAsync( + Guid tenantId, + string displayName, + string adminUrl, + string adminUsername, + string adminPassword, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakComponentConfig config = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + ClusterComponentId = null, + DisplayName = displayName.Trim(), + AdminUrl = adminUrl.TrimEnd('/'), + AdminUsername = adminUsername, + CreatedAt = DateTime.UtcNow, + }; + + db.KeycloakComponentConfigs.Add(config); + await db.SaveChangesAsync(ct); + + await vaultService.SetComponentSecretAsync( + tenantId, config.Id, "KC_BOOTSTRAP_ADMIN_PASSWORD", adminPassword, ct); + + return config; } // ── Realms ──────────────────────────────────────────────────────────────── @@ -1015,6 +1217,9 @@ public class KeycloakService( .FirstOrDefaultAsync(b => b.Id == backupId && b.TenantId == tenantId, ct) ?? throw new InvalidOperationException("Backup not found."); + if (backup.StorageLink is null) + throw new InvalidOperationException("The storage link for this backup has been deleted and the backup cannot be restored."); + byte[] json = await DownloadFromS3Async(tenantId, backup.StorageLink, backup.ObjectKey, ct); if (!string.IsNullOrWhiteSpace(targetRealmName) && targetRealmName != backup.RealmName) @@ -1047,11 +1252,14 @@ public class KeycloakService( .FirstOrDefaultAsync(b => b.Id == backupId && b.TenantId == tenantId, ct) ?? throw new InvalidOperationException("Backup not found."); - try + if (backup.StorageLink is not null) { - await DeleteFromS3Async(tenantId, backup.StorageLink, backup.ObjectKey, ct); + try + { + await DeleteFromS3Async(tenantId, backup.StorageLink, backup.ObjectKey, ct); + } + catch (Exception) { /* best effort */ } } - catch (Exception) { /* best effort */ } db.KeycloakBackups.Remove(backup); await db.SaveChangesAsync(ct); @@ -1098,6 +1306,15 @@ public class KeycloakService( .ToListAsync(ct); } + public async Task GetRealmForAppAsync(Guid tenantId, Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.KeycloakRealms + .Include(r => r.LinkedApp).ThenInclude(a => a!.Customer) + .FirstOrDefaultAsync(r => r.TenantId == tenantId && r.LinkedAppId == appId, ct); + } + public async Task> GetDatabasesForComponentAsync( Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) { @@ -1116,6 +1333,45 @@ public class KeycloakService( .ToListAsync(ct); } + /// + /// Returns registered Postgres databases on the same cluster as a component. + /// Used to populate the database selector in the Keycloak setup form. + /// + public async Task> GetRegisteredPostgresDatabasesForComponentAsync( + Guid tenantId, Guid clusterComponentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent comp = await db.ClusterComponents + .FirstOrDefaultAsync(c => c.Id == clusterComponentId, ct) + ?? throw new InvalidOperationException("Component not found."); + + return await db.RegisteredPostgresDatabases + .Include(d => d.RegisteredPostgresInstance) + .Where(d => d.RegisteredPostgresInstance.TenantId == tenantId + && d.RegisteredPostgresInstance.KubernetesClusterId == comp.ClusterId + && d.Status == RegisteredPostgresDatabaseStatus.Ready) + .OrderBy(d => d.RegisteredPostgresInstance.Name).ThenBy(d => d.Name) + .ToListAsync(ct); + } + + /// + /// Returns all registered Postgres databases for a tenant. + /// Used when the Keycloak instance is external (no cluster component). + /// + public async Task> GetAllRegisteredPostgresDatabasesAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RegisteredPostgresDatabases + .Include(d => d.RegisteredPostgresInstance) + .Where(d => d.RegisteredPostgresInstance.TenantId == tenantId + && d.Status == RegisteredPostgresDatabaseStatus.Ready) + .OrderBy(d => d.RegisteredPostgresInstance.Name).ThenBy(d => d.Name) + .ToListAsync(ct); + } + /// /// Returns CNPG databases available on the given Kubernetes cluster. /// Used to populate the database selector when registering a Keycloak component. @@ -1202,7 +1458,9 @@ public class KeycloakService( "Keycloak admin URL is not configured. Set it in the Identity tab."); } - TokenCacheKey key = new(config.ClusterComponentId); + // For externals (no ClusterComponent), vault secrets are keyed by config.Id. + Guid vaultId = config.ClusterComponentId ?? config.Id; + TokenCacheKey key = new(vaultId); if (_tokenCache.TryGetValue(key, out (string Token, DateTime Expiry) cached) && cached.Expiry > DateTime.UtcNow.AddSeconds(30)) @@ -1214,9 +1472,9 @@ public class KeycloakService( // configured before the rename (imported clusters, pre-26 setups). string? password = await vaultService.GetComponentSecretValueAsync( - tenantId, config.ClusterComponentId, "KC_BOOTSTRAP_ADMIN_PASSWORD", ct) + tenantId, vaultId, "KC_BOOTSTRAP_ADMIN_PASSWORD", ct) ?? await vaultService.GetComponentSecretValueAsync( - tenantId, config.ClusterComponentId, "KEYCLOAK_ADMIN_PASSWORD", ct); + tenantId, vaultId, "KEYCLOAK_ADMIN_PASSWORD", ct); if (string.IsNullOrWhiteSpace(password)) { @@ -1237,7 +1495,12 @@ public class KeycloakService( HttpResponseMessage resp = await http.PostAsync( $"{config.AdminUrl}/realms/master/protocol/openid-connect/token", form, ct); - resp.EnsureSuccessStatusCode(); + if (!resp.IsSuccessStatusCode) + { + string body = await resp.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException( + $"Keycloak token request failed ({(int)resp.StatusCode}): {body}"); + } using JsonDocument doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync(ct)); string token = doc.RootElement.GetProperty("access_token").GetString()!; @@ -1248,6 +1511,147 @@ public class KeycloakService( return token; } + // ── Named themes ───────────────────────────────────────────────────────── + + /// Returns all named themes saved for a Keycloak instance. + public async Task> GetNamedThemesAsync( + Guid tenantId, Guid configId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.KeycloakThemes + .Where(t => t.TenantId == tenantId && t.KeycloakComponentConfigId == configId) + .OrderBy(t => t.Name) + .ToListAsync(ct); + } + + /// Creates a new named theme for a Keycloak instance. + public async Task CreateNamedThemeAsync( + Guid tenantId, Guid configId, string name, + string? loginTheme, string? accountTheme, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakTheme theme = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KeycloakComponentConfigId = configId, + Name = name.Trim(), + LoginTheme = string.IsNullOrWhiteSpace(loginTheme) ? null : loginTheme, + AccountTheme = string.IsNullOrWhiteSpace(accountTheme) ? null : accountTheme, + }; + + db.KeycloakThemes.Add(theme); + await db.SaveChangesAsync(ct); + return theme; + } + + /// Updates the name and native theme properties of a named theme. + public async Task UpdateNamedThemeAsync( + Guid tenantId, Guid themeId, string name, + string? loginTheme, string? accountTheme, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakTheme theme = await db.KeycloakThemes + .FirstOrDefaultAsync(t => t.Id == themeId && t.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Theme not found."); + + theme.Name = name.Trim(); + theme.LoginTheme = string.IsNullOrWhiteSpace(loginTheme) ? null : loginTheme; + theme.AccountTheme = string.IsNullOrWhiteSpace(accountTheme) ? null : accountTheme; + await db.SaveChangesAsync(ct); + } + + /// + /// Deletes a named theme and removes its CSS vault secrets. + /// Realms referencing this theme will have their KeycloakThemeId set to null (SetNull cascade). + /// + public async Task DeleteNamedThemeAsync( + Guid tenantId, Guid themeId, Guid componentId, CancellationToken ct = default) + { + // Remove CSS vault secrets for all theme types before deleting the DB record. + List secrets = await vaultService.GetComponentSecretsAsync(tenantId, componentId, ct); + string prefix = $"named-theme-{themeId}-"; + foreach (VaultSecret s in secrets.Where(s => s.Name.StartsWith(prefix, StringComparison.Ordinal))) + { + await vaultService.DeleteSecretAsync(s.Id, ct); + } + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + KeycloakTheme? theme = await db.KeycloakThemes + .FirstOrDefaultAsync(t => t.Id == themeId && t.TenantId == tenantId, ct); + if (theme is not null) + { + db.KeycloakThemes.Remove(theme); + await db.SaveChangesAsync(ct); + } + } + + /// Returns CSS for the given type from a named theme's vault slot, or null if none saved. + public async Task GetNamedThemeCssAsync( + Guid tenantId, Guid componentId, Guid themeId, string themeType, CancellationToken ct = default) + => await vaultService.GetComponentSecretValueAsync( + tenantId, componentId, $"named-theme-{themeId}-{themeType}-css", ct); + + /// Persists CSS for the given type in a named theme's vault slot (not synced to K8s). + public async Task SaveNamedThemeCssAsync( + Guid tenantId, Guid componentId, Guid themeId, string themeType, string css, + CancellationToken ct = default) + { + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetComponentSecretAsync( + tenantId, componentId, $"named-theme-{themeId}-{themeType}-css", css, ct); + } + + /// + /// Assigns a named theme to a realm and pushes its native login/account themes to Keycloak. + /// Pass null themeId to detach the realm from any named theme (leaves Keycloak native themes as-is). + /// + public async Task AssignNamedThemeToRealmAsync( + Guid tenantId, Guid realmId, Guid? themeId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KeycloakRealm realm = await db.KeycloakRealms + .Include(r => r.ComponentConfig) + .FirstOrDefaultAsync(r => r.Id == realmId && r.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Realm not found."); + + if (themeId is null) + { + realm.KeycloakThemeId = null; + await db.SaveChangesAsync(ct); + return; + } + + KeycloakTheme theme = await db.KeycloakThemes + .FirstOrDefaultAsync(t => t.Id == themeId && t.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Theme not found."); + + // Apply the theme's native login/account theme to Keycloak. + string token = await GetAdminTokenAsync(tenantId, realm.ComponentConfig, ct); + HttpClient http = CreateHttpClient(); + + var patch = new { loginTheme = theme.LoginTheme ?? "", accountTheme = theme.AccountTheme ?? "" }; + + HttpResponseMessage resp = await http.SendAsync(new HttpRequestMessage(HttpMethod.Put, + $"{realm.ComponentConfig.AdminUrl}/admin/realms/{realm.RealmName}") + { + Headers = { Authorization = new AuthenticationHeaderValue("Bearer", token) }, + Content = new StringContent(JsonSerializer.Serialize(patch), Encoding.UTF8, "application/json") + }, ct); + + resp.EnsureSuccessStatusCode(); + + realm.KeycloakThemeId = themeId; + realm.LoginTheme = theme.LoginTheme; + realm.AccountTheme = theme.AccountTheme; + await db.SaveChangesAsync(ct); + } + // ── Themes ─────────────────────────────────────────────────────────────── /// @@ -1317,6 +1721,118 @@ public class KeycloakService( tenantId, clusterComponentId, $"theme-{themeType}-css", css, ct); } + // ── Cross-instance migration ────────────────────────────────────────────── + + /// + /// Copies all users from one realm to another (cross-instance supported). + /// User credentials are not transferred — passwords are hashed and not + /// accessible via the Admin REST API. Users will need to reset their passwords + /// on the target instance. + /// Users that already exist on the target (same username) are skipped. + /// + public async Task CopyUsersAsync( + Guid tenantId, Guid sourceRealmId, Guid targetRealmId, CancellationToken ct = default) + { + (KeycloakRealm sourceRealm, string srcToken) = await LoadRealmAndTokenAsync(tenantId, sourceRealmId, ct); + (KeycloakRealm targetRealm, string dstToken) = await LoadRealmAndTokenAsync(tenantId, targetRealmId, ct); + + HttpClient http = CreateHttpClient(); + CopyResult result = new(); + + static string srcUrl(KeycloakRealm r, string path) => + $"{r.ComponentConfig.AdminUrl}/admin/realms/{r.RealmName}/{path}"; + + // Skip these server-managed fields when posting to the target. + HashSet skip = ["id", "createdTimestamp", "totp", + "disableableCredentialTypes", "requiredActions", "notBefore", "access"]; + + int first = 0; + const int pageSize = 100; + + while (true) + { + HttpResponseMessage getResp = await http.SendAsync( + new HttpRequestMessage(HttpMethod.Get, + srcUrl(sourceRealm, $"users?first={first}&max={pageSize}&briefRepresentation=false")) + { + Headers = { Authorization = new AuthenticationHeaderValue("Bearer", srcToken) } + }, ct); + + if (!getResp.IsSuccessStatusCode) + { + string body = await getResp.Content.ReadAsStringAsync(ct); + throw new InvalidOperationException( + $"Failed to fetch users from source realm ({(int)getResp.StatusCode}): {body}"); + } + + string pageJson = await getResp.Content.ReadAsStringAsync(ct); + using JsonDocument page = JsonDocument.Parse(pageJson); + JsonElement[] users = [.. page.RootElement.EnumerateArray()]; + + if (users.Length == 0) break; + + foreach (JsonElement user in users) + { + string username = user.TryGetProperty("username", out JsonElement un) + ? un.GetString() ?? "" : ""; + + try + { + JsonNode? node = JsonNode.Parse(user.GetRawText()); + if (node is not JsonObject obj) continue; + + foreach (string key in skip) + obj.Remove(key); + + HttpResponseMessage postResp = await http.SendAsync( + new HttpRequestMessage(HttpMethod.Post, + srcUrl(targetRealm, "users")) + { + Headers = { Authorization = new AuthenticationHeaderValue("Bearer", dstToken) }, + Content = new StringContent(obj.ToJsonString(), Encoding.UTF8, "application/json") + }, ct); + + if (postResp.StatusCode == System.Net.HttpStatusCode.Conflict) + result.Skipped++; + else if (!postResp.IsSuccessStatusCode) + result.Errors.Add($"{username}: {(int)postResp.StatusCode} {await postResp.Content.ReadAsStringAsync(ct)}"); + else + result.Copied++; + } + catch (Exception ex) + { + result.Errors.Add($"{username}: {ex.Message}"); + } + } + + if (users.Length < pageSize) break; + first += pageSize; + } + + return result; + } + + /// + /// Copies the CSS overrides for all theme types (login, account, admin, email) + /// stored in EntKube's vault from one Keycloak component to another. + /// Returns the number of theme types that had CSS and were copied. + /// + public async Task CopyThemeCssAsync( + Guid tenantId, Guid sourceComponentId, Guid targetComponentId, CancellationToken ct = default) + { + int copied = 0; + foreach (string themeType in new[] { "login", "account", "admin", "email" }) + { + string? css = await GetThemeCssAsync(tenantId, sourceComponentId, themeType, ct); + if (!string.IsNullOrEmpty(css)) + { + await SaveThemeCssAsync(tenantId, targetComponentId, themeType, css, ct); + copied++; + } + } + return copied; + } + // ── S3 helpers ──────────────────────────────────────────────────────────── private async Task UploadToS3Async( diff --git a/src/EntKube.Web/Services/KubernetesClientFactory.cs b/src/EntKube.Web/Services/KubernetesClientFactory.cs index 1e5be1c..8bd82fa 100644 --- a/src/EntKube.Web/Services/KubernetesClientFactory.cs +++ b/src/EntKube.Web/Services/KubernetesClientFactory.cs @@ -56,6 +56,29 @@ public class KubernetesClientFactory : IKubernetesClientFactory } } + /// + /// Applies a JSON merge patch to a specific Kubernetes resource. + /// Safer than ApplyManifestAsync for partial updates because only the specified fields change; + /// all other fields (including spec.users lists on CRDs) are left untouched. + /// + public async Task PatchJsonAsync( + string resource, string name, string ns, string jsonPatch, string kubeconfig, CancellationToken ct = default) + { + string kubeconfigPath = Path.GetTempFileName(); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, kubeconfig, ct); + + await RunKubectlAsync( + $"patch {resource} {name} -n {ns} --type=merge -p {jsonPatch} --kubeconfig={kubeconfigPath}", ct); + } + finally + { + File.Delete(kubeconfigPath); + } + } + /// /// Creates a namespace if it doesn't already exist. /// @@ -117,7 +140,12 @@ public class KubernetesClientFactory : IKubernetesClientFactory } } - public async Task ExecuteMongoAsync( + public Task ExecuteMongoAsync( + string clusterName, string ns, string script, string kubeconfig, + string? username = null, string? password = null, CancellationToken ct = default) => + ExecuteMongoWithOutputAsync(clusterName, ns, script, kubeconfig, username, password, ct); + + public async Task ExecuteMongoWithOutputAsync( string clusterName, string ns, string script, string kubeconfig, string? username = null, string? password = null, CancellationToken ct = default) { @@ -130,14 +158,11 @@ public class KubernetesClientFactory : IKubernetesClientFactory // MongoDB Community Operator names StatefulSet pods {cluster}-0, {cluster}-1, ... string primaryPod = $"{clusterName}-0"; - // When credentials are provided, authenticate with SCRAM. - // Passwords generated by MongoService are alphanumeric-safe (no shell escaping needed). - string mongoshArgs = !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password) ? $"--username {username} --password {password} --authenticationDatabase admin --quiet" : "--quiet"; - await RunKubectlWithStdinAsync( + return await RunKubectlWithStdinAsync( $"exec -i {primaryPod} -n {ns} --kubeconfig={kubeconfigPath} -- mongosh {mongoshArgs}", script, ct); } @@ -150,9 +175,10 @@ public class KubernetesClientFactory : IKubernetesClientFactory public async Task GetSecretValueAsync( string secretName, string key, string ns, string kubeconfig, CancellationToken ct = default) { + string json = await GetJsonAsync($"secret/{secretName}", ns, kubeconfig, ct: ct); + try { - string json = await GetJsonAsync($"secret/{secretName}", ns, kubeconfig, ct: ct); using JsonDocument doc = JsonDocument.Parse(json); if (doc.RootElement.TryGetProperty("data", out JsonElement data) && data.TryGetProperty(key, out JsonElement valEl)) @@ -163,12 +189,236 @@ public class KubernetesClientFactory : IKubernetesClientFactory } return null; } - catch + catch (Exception ex) when (ex is not InvalidOperationException) { return null; } } + public Task ExecuteSqlOnPodAsync( + string podName, string ns, string sql, string kubeconfig, + string username = "postgres", string? password = null, CancellationToken ct = default) => + ExecuteSqlOnPodCoreAsync(podName, ns, sql, kubeconfig, username, password, ct); + + public async Task ExecuteSqlOnPodWithOutputAsync( + string podName, string ns, string sql, string kubeconfig, + string username = "postgres", string? password = null, CancellationToken ct = default) => + await ExecuteSqlOnPodCoreAsync(podName, ns, sql, kubeconfig, username, password, ct); + + public async Task RunCommandOnPodAsync( + string podName, string ns, IReadOnlyList command, string kubeconfig, + IReadOnlyDictionary? envVars = null, CancellationToken ct = default) + { + string kubeconfigPath = Path.GetTempFileName(); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, kubeconfig, ct); + + ProcessStartInfo startInfo = new() + { + FileName = "kubectl", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + startInfo.ArgumentList.Add("exec"); + startInfo.ArgumentList.Add(podName); + startInfo.ArgumentList.Add("-n"); + startInfo.ArgumentList.Add(ns); + startInfo.ArgumentList.Add($"--kubeconfig={kubeconfigPath}"); + startInfo.ArgumentList.Add("--"); + + if (envVars is { Count: > 0 }) + { + startInfo.ArgumentList.Add("env"); + foreach (KeyValuePair kv in envVars) + startInfo.ArgumentList.Add($"{kv.Key}={kv.Value}"); + } + + foreach (string arg in command) + startInfo.ArgumentList.Add(arg); + + using Process process = new() { StartInfo = startInfo }; + process.Start(); + + Task outputTask = process.StandardOutput.ReadToEndAsync(ct); + Task errorTask = process.StandardError.ReadToEndAsync(ct); + + await process.WaitForExitAsync(ct); + + string output = await outputTask; + string error = await errorTask; + + if (process.ExitCode != 0) + throw new InvalidOperationException($"kubectl exec failed (exit {process.ExitCode}): {error}"); + + return output; + } + finally + { + File.Delete(kubeconfigPath); + } + } + + public async Task ExecuteSqlInCnpgDatabaseAsync( + string clusterName, string ns, string database, string sql, string kubeconfig, CancellationToken ct = default) + { + await ExecuteSqlInCnpgDatabaseWithOutputAsync(clusterName, ns, database, sql, kubeconfig, ct); + } + + public async Task ExecuteSqlInCnpgDatabaseWithOutputAsync( + string clusterName, string ns, string database, string sql, string kubeconfig, CancellationToken ct = default) + { + string kubeconfigPath = Path.GetTempFileName(); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, kubeconfig, ct); + + string primaryPod = $"{clusterName}-1"; + + return await RunKubectlWithStdinAsync( + $"exec -i {primaryPod} -n {ns} --kubeconfig={kubeconfigPath} -- psql -U postgres -d \"{database}\" -t -A", + sql, ct); + } + finally + { + File.Delete(kubeconfigPath); + } + } + + public async Task RunCommandOnPodWithStdinAsync( + string podName, string ns, IReadOnlyList command, string stdin, string kubeconfig, + CancellationToken ct = default) + { + string kubeconfigPath = Path.GetTempFileName(); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, kubeconfig, ct); + + ProcessStartInfo startInfo = new() + { + FileName = "kubectl", + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + startInfo.ArgumentList.Add("exec"); + startInfo.ArgumentList.Add("-i"); + startInfo.ArgumentList.Add(podName); + startInfo.ArgumentList.Add("-n"); + startInfo.ArgumentList.Add(ns); + startInfo.ArgumentList.Add($"--kubeconfig={kubeconfigPath}"); + startInfo.ArgumentList.Add("--"); + + foreach (string arg in command) + startInfo.ArgumentList.Add(arg); + + using Process process = new() { StartInfo = startInfo }; + process.Start(); + + await process.StandardInput.WriteAsync(stdin.AsMemory(), ct); + await process.StandardInput.FlushAsync(ct); + process.StandardInput.Close(); + + Task outputTask = process.StandardOutput.ReadToEndAsync(ct); + Task errorTask = process.StandardError.ReadToEndAsync(ct); + + await process.WaitForExitAsync(ct); + + string output = await outputTask; + string error = await errorTask; + + if (process.ExitCode != 0) + throw new InvalidOperationException($"kubectl exec failed (exit {process.ExitCode}): {error}"); + + return output; + } + finally + { + File.Delete(kubeconfigPath); + } + } + + private async Task ExecuteSqlOnPodCoreAsync( + string podName, string ns, string sql, string kubeconfig, + string username, string? password, CancellationToken ct) + { + string kubeconfigPath = Path.GetTempFileName(); + + try + { + await File.WriteAllTextAsync(kubeconfigPath, kubeconfig, ct); + + // Use ArgumentList (not Arguments string) so passwords with special + // characters are never mangled by shell quoting or .NET arg splitting. + ProcessStartInfo startInfo = new() + { + FileName = "kubectl", + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + startInfo.ArgumentList.Add("exec"); + startInfo.ArgumentList.Add("-i"); + startInfo.ArgumentList.Add(podName); + startInfo.ArgumentList.Add("-n"); + startInfo.ArgumentList.Add(ns); + startInfo.ArgumentList.Add($"--kubeconfig={kubeconfigPath}"); + startInfo.ArgumentList.Add("--"); + + if (!string.IsNullOrEmpty(password)) + { + startInfo.ArgumentList.Add("env"); + startInfo.ArgumentList.Add($"PGPASSWORD={password}"); + } + + startInfo.ArgumentList.Add("psql"); + startInfo.ArgumentList.Add("-U"); + startInfo.ArgumentList.Add(username); + // Force TCP so pg_hba host rules apply (not peer/local socket rules). + startInfo.ArgumentList.Add("-h"); + startInfo.ArgumentList.Add("127.0.0.1"); + startInfo.ArgumentList.Add("-t"); + + using Process process = new() { StartInfo = startInfo }; + process.Start(); + + await process.StandardInput.WriteAsync(sql.AsMemory(), ct); + await process.StandardInput.FlushAsync(ct); + process.StandardInput.Close(); + + Task outputTask = process.StandardOutput.ReadToEndAsync(ct); + Task errorTask = process.StandardError.ReadToEndAsync(ct); + + await process.WaitForExitAsync(ct); + + string output = await outputTask; + string error = await errorTask; + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"kubectl failed (exit {process.ExitCode}): {error}"); + } + + return output; + } + finally + { + File.Delete(kubeconfigPath); + } + } + private static async Task RunKubectlAsync(string arguments, CancellationToken ct) { using Process process = new() diff --git a/src/EntKube.Web/Services/KubernetesOperationsService.cs b/src/EntKube.Web/Services/KubernetesOperationsService.cs index 05c0c66..9a079fa 100644 --- a/src/EntKube.Web/Services/KubernetesOperationsService.cs +++ b/src/EntKube.Web/Services/KubernetesOperationsService.cs @@ -2,6 +2,7 @@ using EntKube.Web.Data; using k8s; using k8s.Models; using Microsoft.EntityFrameworkCore; +using System.IO; namespace EntKube.Web.Services; @@ -73,7 +74,10 @@ public class ContainerInfo /// 3. Creates a K8s client and performs the operation /// 4. Returns a Result so the UI can show success/failure gracefully /// -public class KubernetesOperationsService(IDbContextFactory dbFactory) +public class KubernetesOperationsService( + IDbContextFactory dbFactory, + AuditService auditService, + ILogger logger) { /// /// Loads a deployment with its cluster attached, so we know where @@ -207,6 +211,7 @@ public class KubernetesOperationsService(IDbContextFactory public async Task RestartDeploymentAsync( Guid deploymentId, string k8sDeploymentName, + string? performedBy = null, CancellationToken ct = default) { AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); @@ -237,10 +242,20 @@ public class KubernetesOperationsService(IDbContextFactory await client.AppsV1.PatchNamespacedDeploymentAsync( patch, k8sDeploymentName, deployment.Namespace, cancellationToken: ct); + logger.LogInformation( + "Deployment {K8sName} in {Namespace} restarted by {User}", + k8sDeploymentName, deployment.Namespace, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, "RestartDeployment", "Deployment", + k8sDeploymentName, performedBy: performedBy, ct: ct); + return KubernetesOperationResult.Success(); } catch (Exception ex) { + logger.LogWarning(ex, + "Failed to restart deployment {K8sName} in {Namespace}", + k8sDeploymentName, deployment.Namespace); return KubernetesOperationResult.Failure($"Failed to restart deployment: {ex.Message}"); } } @@ -253,6 +268,7 @@ public class KubernetesOperationsService(IDbContextFactory public async Task DeletePodAsync( Guid deploymentId, string podName, + string? performedBy = null, CancellationToken ct = default) { AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); @@ -275,10 +291,19 @@ public class KubernetesOperationsService(IDbContextFactory await client.CoreV1.DeleteNamespacedPodAsync( podName, deployment.Namespace, cancellationToken: ct); + logger.LogInformation( + "Pod {PodName} in {Namespace} deleted by {User}", + podName, deployment.Namespace, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, "DeletePod", "Pod", + podName, performedBy: performedBy, ct: ct); + return KubernetesOperationResult.Success(); } catch (Exception ex) { + logger.LogWarning(ex, "Failed to delete pod {PodName} in {Namespace}", + podName, deployment.Namespace); return KubernetesOperationResult.Failure($"Failed to delete pod: {ex.Message}"); } } @@ -291,6 +316,7 @@ public class KubernetesOperationsService(IDbContextFactory Guid deploymentId, string k8sDeploymentName, int replicas, + string? performedBy = null, CancellationToken ct = default) { AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); @@ -317,14 +343,838 @@ public class KubernetesOperationsService(IDbContextFactory await client.AppsV1.PatchNamespacedDeploymentAsync( patch, k8sDeploymentName, deployment.Namespace, cancellationToken: ct); + logger.LogInformation( + "Deployment {K8sName} in {Namespace} scaled to {Replicas} by {User}", + k8sDeploymentName, deployment.Namespace, replicas, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, "ScaleDeployment", "Deployment", + k8sDeploymentName, $"replicas={replicas}", performedBy, ct); + return KubernetesOperationResult.Success(); } catch (Exception ex) { + logger.LogWarning(ex, "Failed to scale deployment {K8sName} in {Namespace}", + k8sDeploymentName, deployment.Namespace); return KubernetesOperationResult.Failure($"Failed to scale deployment: {ex.Message}"); } } + // ──────── Resource tree actions ──────── + + /// + /// Returns the container names for a specific pod — used to populate the + /// container picker in the log viewer for multi-container pods. + /// + public async Task>> GetPodContainersAsync( + Guid deploymentId, string podName, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + if (deployment is null) + return KubernetesOperationResult>.Failure("Deployment not found."); + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult>.Failure( + "Cluster has no kubeconfig configured."); + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + V1Pod pod = await client.CoreV1.ReadNamespacedPodAsync( + podName, deployment.Namespace, cancellationToken: ct); + List containers = pod.Spec?.Containers? + .Select(c => c.Name).ToList() ?? []; + return KubernetesOperationResult>.Success(containers); + } + catch (Exception ex) + { + return KubernetesOperationResult>.Failure( + $"Failed to read pod: {ex.Message}"); + } + } + + /// + /// Retrieves recent Kubernetes Events for a named resource. Events surface + /// scheduling failures, image pull errors, and readiness probe results — + /// the same information shown by "kubectl describe". + /// + public async Task>> GetResourceEventsAsync( + Guid deploymentId, string kind, string name, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + if (deployment is null) + return KubernetesOperationResult>.Failure("Deployment not found."); + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult>.Failure( + "Cluster has no kubeconfig configured."); + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + var events = await client.CoreV1.ListNamespacedEventAsync( + deployment.Namespace, cancellationToken: ct); + + List lines = events.Items + .Where(e => e.InvolvedObject?.Name == name + && e.InvolvedObject?.Kind == kind) + .OrderBy(e => e.LastTimestamp ?? e.EventTime) + .Select(e => + { + string ts = (e.LastTimestamp ?? e.EventTime)?.ToString("HH:mm:ss") ?? "?"; + string type = e.Type ?? "Normal"; + string reason = e.Reason ?? ""; + string msg = e.Message ?? ""; + return $"{ts} {type,-8} {reason,-24} {msg}"; + }) + .ToList(); + + return KubernetesOperationResult>.Success(lines); + } + catch (Exception ex) + { + return KubernetesOperationResult>.Failure( + $"Failed to list events: {ex.Message}"); + } + } + + /// + /// Scales a workload (Deployment, StatefulSet, or ReplicaSet) to the specified + /// replica count. Unified entry point so callers don't branch on kind. + /// + public async Task ScaleWorkloadAsync( + Guid deploymentId, string kind, string name, int replicas, + string? performedBy = null, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult.Failure("Cluster has no kubeconfig configured."); + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + V1Patch patch = new( + $"{{\"spec\":{{\"replicas\":{replicas}}}}}", + V1Patch.PatchType.MergePatch); + + switch (kind) + { + case "Deployment": + await client.AppsV1.PatchNamespacedDeploymentAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + case "StatefulSet": + await client.AppsV1.PatchNamespacedStatefulSetAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + case "ReplicaSet": + await client.AppsV1.PatchNamespacedReplicaSetAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + default: + return KubernetesOperationResult.Failure($"Cannot scale {kind}."); + } + + logger.LogInformation("{Kind} {Name} scaled to {Replicas} in {Namespace} by {User}", + kind, name, replicas, deployment.Namespace, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, "ScaleWorkload", kind, + name, $"replicas={replicas}", performedBy, ct); + + return KubernetesOperationResult.Success(); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Scale failed for {Kind} {Name} in {Namespace}", kind, name, deployment.Namespace); + return KubernetesOperationResult.Failure($"Scale failed: {ex.Message}"); + } + } + + /// + /// Restarts a workload (Deployment, StatefulSet, or DaemonSet) by patching + /// the pod-template annotation — identical to "kubectl rollout restart". + /// + public async Task RestartWorkloadAsync( + Guid deploymentId, string kind, string name, + string? performedBy = null, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult.Failure("Cluster has no kubeconfig configured."); + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + V1Patch patch = new( + "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":" + + $"{{\"kubectl.kubernetes.io/restartedAt\":\"{DateTime.UtcNow:O}\"}}" + + "}}}}", + V1Patch.PatchType.MergePatch); + + switch (kind) + { + case "Deployment": + await client.AppsV1.PatchNamespacedDeploymentAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + case "StatefulSet": + await client.AppsV1.PatchNamespacedStatefulSetAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + case "DaemonSet": + await client.AppsV1.PatchNamespacedDaemonSetAsync( + patch, name, deployment.Namespace, cancellationToken: ct); + break; + default: + return KubernetesOperationResult.Failure($"Cannot restart {kind}."); + } + + logger.LogInformation("{Kind} {Name} restarted in {Namespace} by {User}", + kind, name, deployment.Namespace, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, "RestartWorkload", kind, + name, performedBy: performedBy, ct: ct); + + return KubernetesOperationResult.Success(); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Restart failed for {Kind} {Name} in {Namespace}", kind, name, deployment.Namespace); + return KubernetesOperationResult.Failure($"Restart failed: {ex.Message}"); + } + } + + /// + /// Deletes a resource by kind — Pod (triggers replacement) or Job. + /// + public async Task DeleteResourceAsync( + Guid deploymentId, string kind, string name, + string? performedBy = null, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult.Failure("Cluster has no kubeconfig configured."); + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + + switch (kind) + { + case "Pod": + await client.CoreV1.DeleteNamespacedPodAsync( + name, deployment.Namespace, cancellationToken: ct); + break; + case "Job": + await client.BatchV1.DeleteNamespacedJobAsync( + name, deployment.Namespace, + body: new V1DeleteOptions { PropagationPolicy = "Background" }, + cancellationToken: ct); + break; + default: + return KubernetesOperationResult.Failure($"Delete not supported for {kind}."); + } + + logger.LogInformation("{Kind} {Name} deleted in {Namespace} by {User}", + kind, name, deployment.Namespace, performedBy ?? "system"); + + await auditService.RecordAsync(deploymentId, $"Delete{kind}", kind, + name, performedBy: performedBy, ct: ct); + + return KubernetesOperationResult.Success(); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Delete failed for {Kind} {Name} in {Namespace}", kind, name, deployment.Namespace); + return KubernetesOperationResult.Failure($"Delete failed: {ex.Message}"); + } + } + + // ──────── YAML / Manual apply ──────── + + /// + /// Applies all manifests for a YAML or Manual AppDeployment to the cluster + /// using "kubectl apply". Idempotent: creates resources on first run, patches + /// them on subsequent runs (standard server-side apply semantics). + /// + /// A bare Namespace manifest is prepended automatically so the target namespace + /// is created if it doesn't exist — equivalent to Helm's --create-namespace. + /// + /// Manifests are applied in SortOrder sequence so foundational resources + /// (PVCs, ConfigMaps) land before workloads (Deployments, Services). + /// + public async Task> ApplyYamlDeploymentAsync( + Guid deploymentId, string? performedBy = null, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + + if (string.IsNullOrWhiteSpace(deployment.Cluster.Kubeconfig)) + return KubernetesOperationResult.Failure( + "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations."); + + // Load manifests in apply order. + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List manifests = await db.DeploymentManifests + .Where(m => m.DeploymentId == deploymentId) + .OrderBy(m => m.SortOrder) + .ToListAsync(ct); + + if (manifests.Count == 0) + return KubernetesOperationResult.Failure( + "No manifests defined for this deployment. Add YAML manifests before applying."); + + // Prepend a Namespace manifest so the namespace is created automatically + // if it doesn't exist yet — mirrors Helm's --create-namespace behaviour. + string nsManifest = $"apiVersion: v1\nkind: Namespace\nmetadata:\n name: {deployment.Namespace}"; + string combined = nsManifest + "\n---\n" + string.Join("\n---\n", manifests.Select(m => m.YamlContent)); + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + string tempManifest = Path.Combine(Path.GetTempPath(), $"entkube-manifest-{Guid.NewGuid()}.yaml"); + + try + { + await File.WriteAllTextAsync(tempKubeconfig, deployment.Cluster.Kubeconfig, ct); + await File.WriteAllTextAsync(tempManifest, combined, ct); + + HelmExecutionResult result = await RunCliAsync( + "kubectl", + $"apply -f {tempManifest} --kubeconfig {tempKubeconfig}", + ct); + + if (result.Success) + { + logger.LogInformation("YAML deployment {DeploymentId} applied to {Namespace} by {User}", + deploymentId, deployment.Namespace, performedBy ?? "system"); + await auditService.RecordAsync(deploymentId, "ApplyYaml", "Deployment", + deployment.Name, performedBy: performedBy, ct: ct); + } + else + { + logger.LogWarning("YAML apply failed for deployment {DeploymentId}: {Output}", + deploymentId, result.Output); + } + + return result.Success + ? KubernetesOperationResult.Success(result.Output) + : KubernetesOperationResult.Failure(result.Output); + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + if (File.Exists(tempManifest)) File.Delete(tempManifest); + } + } + + // ──────── Helm install / upgrade ──────── + + /// + /// Runs "helm upgrade --install" for a HelmChart AppDeployment. + /// Idempotent: installs on first run, upgrades on subsequent ones. + /// Always passes --create-namespace so the target namespace is created + /// automatically if it doesn't exist yet. + /// + /// If a HelmRepoUrl is configured the repo is registered (helm repo add) + /// and updated before the install. The release name is derived from the + /// deployment name, sanitised to Helm's DNS-compatible format. + /// + /// Returns the combined stdout + stderr from the helm process so the UI + /// can show exactly what Helm reported. + /// + public async Task> HelmInstallOrUpgradeAsync( + Guid deploymentId, string? performedBy = null, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + + if (string.IsNullOrWhiteSpace(deployment.Cluster.Kubeconfig)) + return KubernetesOperationResult.Failure( + "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations."); + + if (string.IsNullOrWhiteSpace(deployment.HelmChartName)) + return KubernetesOperationResult.Failure( + "No Helm chart name configured. Set the chart name in the deployment settings before installing."); + + string releaseName = ToHelmReleaseName(deployment.Name); + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + string? tempValues = null; + + try + { + await File.WriteAllTextAsync(tempKubeconfig, deployment.Cluster.Kubeconfig, ct); + + // Register and update the Helm repo if a URL is given. + string chartRef = deployment.HelmChartName; + + if (!string.IsNullOrWhiteSpace(deployment.HelmRepoUrl)) + { + string repoAlias = $"entkube-{releaseName}"; + + await RunCliAsync("helm", + $"repo add {repoAlias} {deployment.HelmRepoUrl} --force-update --kubeconfig {tempKubeconfig}", ct); + await RunCliAsync("helm", + $"repo update {repoAlias} --kubeconfig {tempKubeconfig}", ct); + + chartRef = $"{repoAlias}/{deployment.HelmChartName}"; + } + + // Build the main helm upgrade --install command. + List args = + [ + "upgrade", "--install", + releaseName, + chartRef, + "--namespace", deployment.Namespace, + "--create-namespace", + ]; + + if (!string.IsNullOrWhiteSpace(deployment.HelmChartVersion)) + { + args.Add("--version"); + args.Add(deployment.HelmChartVersion); + } + + if (!string.IsNullOrWhiteSpace(deployment.HelmValues)) + { + tempValues = Path.Combine(Path.GetTempPath(), $"entkube-values-{Guid.NewGuid()}.yaml"); + await File.WriteAllTextAsync(tempValues, deployment.HelmValues, ct); + args.Add("--values"); + args.Add(tempValues); + } + + args.Add("--kubeconfig"); + args.Add(tempKubeconfig); + args.Add("--wait"); + args.Add("--timeout"); + args.Add("10m0s"); + + HelmExecutionResult result = await RunCliAsync("helm", string.Join(" ", args), ct); + + if (result.Success) + { + logger.LogInformation( + "Helm install/upgrade {Chart} {Version} to {Namespace} succeeded for deployment {DeploymentId} by {User}", + deployment.HelmChartName, deployment.HelmChartVersion, + deployment.Namespace, deploymentId, performedBy ?? "system"); + await auditService.RecordAsync(deploymentId, "HelmInstallOrUpgrade", "HelmRelease", + deployment.Name, $"{deployment.HelmChartName}@{deployment.HelmChartVersion}", performedBy, ct); + } + else + { + logger.LogWarning( + "Helm install/upgrade failed for deployment {DeploymentId}: {Output}", + deploymentId, result.Output); + } + + return result.Success + ? KubernetesOperationResult.Success(result.Output) + : KubernetesOperationResult.Failure(result.Output); + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + if (tempValues is not null && File.Exists(tempValues)) File.Delete(tempValues); + } + } + + /// + /// Queries the cluster live and returns a proper ownership tree of resources + /// found in the deployment's namespace. OwnerReferences are used to wire up + /// parent-child relationships (e.g. Deployment → ReplicaSet → Pod). + /// Each resource type is fetched independently so a missing API group + /// (e.g. no Ingress controller) doesn't block the others. + /// + public async Task>> GetLiveResourcesAsync( + Guid deploymentId, CancellationToken ct = default) + { + AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct); + + if (deployment is null) + return KubernetesOperationResult>.Failure("Deployment not found."); + + if (string.IsNullOrEmpty(deployment.Cluster?.Kubeconfig)) + return KubernetesOperationResult>.Failure( + "Cluster has no kubeconfig configured."); + + string ns = deployment.Namespace; + + // k8s UID → our resource node (for O(1) parent lookup during tree wiring) + Dictionary byUid = new(); + // Resources whose OwnerReferences need to be resolved after all types are fetched. + List<(DeploymentResource Node, string[] OwnerUids)> pendingWire = []; + + // Create a resource node, record its UID, and register its owner refs for later wiring. + DeploymentResource MakeNode(string? uid, IList? owners, + string group, string version, string kind, string name, + HealthStatus health, string? message) + { + DeploymentResource r = new() + { + Id = Guid.NewGuid(), + DeploymentId = deploymentId, + Group = group, Version = version, Kind = kind, + Name = name, Namespace = ns, + SyncStatus = SyncStatus.Synced, + HealthStatus = health, + StatusMessage = message, + ChildResources = [] + }; + + if (!string.IsNullOrEmpty(uid)) + byUid[uid] = r; + + string[] ownerUids = owners? + .Select(o => o.Uid).Where(u => !string.IsNullOrEmpty(u)).ToArray() ?? []; + + if (ownerUids.Length > 0) + pendingWire.Add((r, ownerUids)); + + return r; + } + + try + { + using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig); + + // ── Deployments ─────────────────────────────────────────────── + await TryFetch(async () => + { + V1DeploymentList list = await client.AppsV1.ListNamespacedDeploymentAsync(ns, cancellationToken: ct); + foreach (V1Deployment d in list.Items) + { + int desired = d.Spec?.Replicas ?? 0; + int ready = d.Status?.ReadyReplicas ?? 0; + HealthStatus h = desired == 0 ? HealthStatus.Suspended + : ready >= desired ? HealthStatus.Healthy + : ready > 0 ? HealthStatus.Degraded + : HealthStatus.Progressing; + MakeNode(d.Metadata.Uid, d.Metadata.OwnerReferences, + "apps", "v1", "Deployment", d.Metadata.Name, h, $"{ready}/{desired} ready"); + } + }); + + // ── StatefulSets ───────────────────────────────────────────── + await TryFetch(async () => + { + V1StatefulSetList list = await client.AppsV1.ListNamespacedStatefulSetAsync(ns, cancellationToken: ct); + foreach (V1StatefulSet s in list.Items) + { + int desired = s.Spec?.Replicas ?? 0; + int ready = s.Status?.ReadyReplicas ?? 0; + HealthStatus h = desired == 0 ? HealthStatus.Suspended + : ready >= desired ? HealthStatus.Healthy + : ready > 0 ? HealthStatus.Degraded + : HealthStatus.Progressing; + MakeNode(s.Metadata.Uid, s.Metadata.OwnerReferences, + "apps", "v1", "StatefulSet", s.Metadata.Name, h, $"{ready}/{desired} ready"); + } + }); + + // ── DaemonSets ─────────────────────────────────────────────── + await TryFetch(async () => + { + V1DaemonSetList list = await client.AppsV1.ListNamespacedDaemonSetAsync(ns, cancellationToken: ct); + foreach (V1DaemonSet ds in list.Items) + { + int desired = ds.Status?.DesiredNumberScheduled ?? 0; + int ready = ds.Status?.NumberReady ?? 0; + HealthStatus h = desired == 0 ? HealthStatus.Suspended + : ready >= desired ? HealthStatus.Healthy + : ready > 0 ? HealthStatus.Degraded + : HealthStatus.Progressing; + MakeNode(ds.Metadata.Uid, ds.Metadata.OwnerReferences, + "apps", "v1", "DaemonSet", ds.Metadata.Name, h, $"{ready}/{desired} ready"); + } + }); + + // ── ReplicaSets (ALL — including 0-replica old ones for tree completeness) ─ + await TryFetch(async () => + { + V1ReplicaSetList list = await client.AppsV1.ListNamespacedReplicaSetAsync(ns, cancellationToken: ct); + foreach (V1ReplicaSet rs in list.Items) + { + int desired = rs.Spec?.Replicas ?? 0; + int ready = rs.Status?.ReadyReplicas ?? 0; + HealthStatus h = desired == 0 ? HealthStatus.Suspended + : ready >= desired ? HealthStatus.Healthy + : ready > 0 ? HealthStatus.Degraded + : HealthStatus.Progressing; + MakeNode(rs.Metadata.Uid, rs.Metadata.OwnerReferences, + "apps", "v1", "ReplicaSet", rs.Metadata.Name, h, $"{ready}/{desired} ready"); + } + }); + + // ── Services ──────────────────────────────────────────────── + await TryFetch(async () => + { + V1ServiceList list = await client.CoreV1.ListNamespacedServiceAsync(ns, cancellationToken: ct); + foreach (V1Service svc in list.Items) + { + MakeNode(svc.Metadata.Uid, svc.Metadata.OwnerReferences, + "", "v1", "Service", svc.Metadata.Name, HealthStatus.Healthy, + $"{svc.Spec?.Type ?? "ClusterIP"} {svc.Spec?.ClusterIP}"); + } + }); + + // ── Ingresses ─────────────────────────────────────────────── + await TryFetch(async () => + { + V1IngressList list = await client.NetworkingV1.ListNamespacedIngressAsync(ns, cancellationToken: ct); + foreach (V1Ingress ing in list.Items) + { + string? lb = ing.Status?.LoadBalancer?.Ingress?.FirstOrDefault()?.Ip + ?? ing.Status?.LoadBalancer?.Ingress?.FirstOrDefault()?.Hostname; + string hosts = string.Join(", ", (ing.Spec?.Rules?.Select(r => r.Host) ?? []) + .Where(h => !string.IsNullOrEmpty(h))!); + MakeNode(ing.Metadata.Uid, ing.Metadata.OwnerReferences, + "networking.k8s.io", "v1", "Ingress", ing.Metadata.Name, HealthStatus.Healthy, + lb ?? hosts); + } + }); + + // ── PersistentVolumeClaims ─────────────────────────────────── + await TryFetch(async () => + { + V1PersistentVolumeClaimList list = await client.CoreV1.ListNamespacedPersistentVolumeClaimAsync(ns, cancellationToken: ct); + foreach (V1PersistentVolumeClaim pvc in list.Items) + { + HealthStatus h = pvc.Status?.Phase == "Bound" ? HealthStatus.Healthy + : pvc.Status?.Phase == "Pending" ? HealthStatus.Progressing + : HealthStatus.Degraded; + string? size = pvc.Spec?.Resources?.Requests is { } req && + req.TryGetValue("storage", out ResourceQuantity? qty) + ? qty?.ToString() : null; + MakeNode(pvc.Metadata.Uid, pvc.Metadata.OwnerReferences, + "", "v1", "PersistentVolumeClaim", pvc.Metadata.Name, h, + string.Join(" ", new[] { pvc.Status?.Phase, size }.Where(s => s != null))); + } + }); + + // ── ConfigMaps (exclude system-generated and owned ones) ───── + await TryFetch(async () => + { + V1ConfigMapList list = await client.CoreV1.ListNamespacedConfigMapAsync(ns, cancellationToken: ct); + foreach (V1ConfigMap cm in list.Items.Where(c => + c.Metadata.Name != "kube-root-ca.crt" && + c.Metadata.OwnerReferences?.Any() != true)) + { + MakeNode(cm.Metadata.Uid, null, + "", "v1", "ConfigMap", cm.Metadata.Name, HealthStatus.Healthy, null); + } + }); + + // ── Jobs ───────────────────────────────────────────────────── + await TryFetch(async () => + { + V1JobList list = await client.BatchV1.ListNamespacedJobAsync(ns, cancellationToken: ct); + foreach (V1Job job in list.Items) + { + bool active = (job.Status?.Active ?? 0) > 0; + bool succeeded = (job.Status?.Succeeded ?? 0) > 0; + bool failed = (job.Status?.Failed ?? 0) > 0; + HealthStatus h = failed ? HealthStatus.Degraded + : active ? HealthStatus.Progressing + : succeeded ? HealthStatus.Healthy + : HealthStatus.Suspended; + MakeNode(job.Metadata.Uid, job.Metadata.OwnerReferences, + "batch", "v1", "Job", job.Metadata.Name, h, + $"✓{job.Status?.Succeeded ?? 0} ✗{job.Status?.Failed ?? 0}"); + } + }); + + // ── CronJobs ────────────────────────────────────────────────── + await TryFetch(async () => + { + V1CronJobList list = await client.BatchV1.ListNamespacedCronJobAsync(ns, cancellationToken: ct); + foreach (V1CronJob cj in list.Items) + { + MakeNode(cj.Metadata.Uid, null, + "batch", "v1", "CronJob", cj.Metadata.Name, + cj.Spec?.Suspend == true ? HealthStatus.Suspended : HealthStatus.Healthy, + cj.Spec?.Schedule); + } + }); + + // ── Pods ────────────────────────────────────────────────────── + await TryFetch(async () => + { + V1PodList list = await client.CoreV1.ListNamespacedPodAsync(ns, cancellationToken: ct); + foreach (V1Pod pod in list.Items) + { + int ready = pod.Status?.ContainerStatuses?.Count(cs => cs.Ready) ?? 0; + int total = pod.Spec?.Containers?.Count ?? 0; + HealthStatus h = pod.Status?.Phase switch + { + "Running" => pod.Status.ContainerStatuses?.All(cs => cs.Ready) == true + ? HealthStatus.Healthy : HealthStatus.Degraded, + "Pending" => HealthStatus.Progressing, + "Succeeded" => HealthStatus.Healthy, + "Failed" => HealthStatus.Degraded, + _ => HealthStatus.Unknown + }; + MakeNode(pod.Metadata.Uid, pod.Metadata.OwnerReferences, + "", "v1", "Pod", pod.Metadata.Name, h, + $"{pod.Status?.Phase} {ready}/{total}"); + } + }); + + // ── Wire parent-child using OwnerReferences ─────────────────── + foreach ((DeploymentResource node, string[] ownerUids) in pendingWire) + { + foreach (string ownerUid in ownerUids) + { + if (byUid.TryGetValue(ownerUid, out DeploymentResource? parent)) + { + node.ParentResourceId = parent.Id; + parent.ChildResources.Add(node); + break; + } + } + } + + // ── Return root nodes sorted by display order then name ─────── + List roots = byUid.Values + .Where(r => r.ParentResourceId == null) + .OrderBy(r => KindDisplayOrder(r.Kind)) + .ThenBy(r => r.Name) + .ToList(); + + SortChildResources(roots); + + return KubernetesOperationResult>.Success(roots); + } + catch (Exception ex) + { + return KubernetesOperationResult>.Failure( + $"Failed to query cluster: {ex.Message}"); + } + } + + /// + /// Derives the overall sync + health status from a live resource tree. + /// Called after GetLiveResourcesAsync so the UI can persist and display + /// up-to-date status badges without a second cluster round-trip. + /// + /// Rules: + /// - Empty tree → Unknown / Unknown + /// - Any resources present → Synced + /// - Health = worst-case across all root workload nodes + /// (Degraded > Progressing > Suspended > Healthy > Unknown) + /// + public static (SyncStatus Sync, HealthStatus Health) ComputeStatusFromResources( + List roots) + { + if (roots.Count == 0) + return (SyncStatus.Unknown, HealthStatus.Unknown); + + // Prefer workload kinds for the aggregate; fall back to all roots. + List workloads = roots + .Where(r => r.Kind is "Deployment" or "StatefulSet" or "DaemonSet") + .ToList(); + + if (workloads.Count == 0) + workloads = roots; + + static int HealthSeverity(HealthStatus h) => h switch + { + HealthStatus.Degraded => 4, + HealthStatus.Missing => 4, + HealthStatus.Progressing => 3, + HealthStatus.Suspended => 2, + HealthStatus.Healthy => 1, + _ => 0, + }; + + HealthStatus worst = workloads + .OrderByDescending(w => HealthSeverity(w.HealthStatus)) + .First() + .HealthStatus; + + // Map Missing → Degraded for the top-level badge (Missing is a resource-level concept). + if (worst == HealthStatus.Missing) + worst = HealthStatus.Degraded; + + return (SyncStatus.Synced, worst); + } + + /// + /// Derives sync + health status from a list of live pods. + /// Used in portal views that already have the pod list available. + /// + public static (SyncStatus Sync, HealthStatus Health) ComputeStatusFromPods( + List pods) + { + if (pods.Count == 0) + return (SyncStatus.Unknown, HealthStatus.Unknown); + + bool allReady = pods.All(p => + p.Status == "Running" && p.ReadyContainers == p.TotalContainers); + bool anyFailed = pods.Any(p => p.Status == "Failed"); + bool anyPending = pods.Any(p => p.Status == "Pending"); + bool anyRunning = pods.Any(p => p.Status == "Running"); + + HealthStatus health = + anyFailed ? HealthStatus.Degraded + : allReady ? HealthStatus.Healthy + : anyPending ? HealthStatus.Progressing + : anyRunning ? HealthStatus.Degraded // running but not all ready + : HealthStatus.Unknown; + + return (SyncStatus.Synced, health); + } + + private static void SortChildResources(IEnumerable nodes) + { + foreach (DeploymentResource node in nodes) + { + if (node.ChildResources is List kids && kids.Count > 1) + { + kids.Sort((a, b) => + { + int ko = KindDisplayOrder(a.Kind).CompareTo(KindDisplayOrder(b.Kind)); + return ko != 0 ? ko : string.Compare(a.Name, b.Name, StringComparison.Ordinal); + }); + SortChildResources(kids); + } + } + } + + private static int KindDisplayOrder(string kind) => kind switch + { + "Deployment" => 0, + "StatefulSet" => 1, + "DaemonSet" => 2, + "CronJob" => 3, + "Job" => 4, + "Service" => 5, + "Ingress" => 6, + "PersistentVolumeClaim" => 7, + "ConfigMap" => 8, + "ReplicaSet" => 9, + "Pod" => 10, + _ => 99 + }; + + /// Runs a fetch action and swallows errors — a missing API group + /// (e.g. no Ingress controller) should not abort the entire resource list. + private async Task TryFetch(Func fetch, string resourceType = "") + { + try { await fetch(); } + catch (Exception ex) + { + logger.LogDebug(ex, "Non-fatal: could not fetch {ResourceType} from cluster", resourceType); + } + } + // ──────── Internal ──────── /// @@ -337,4 +1187,64 @@ public class KubernetesOperationsService(IDbContextFactory KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream); return new Kubernetes(config); } + + /// + /// Converts an arbitrary deployment name into a Helm-compatible release name: + /// lowercase, alphanumeric + hyphens only, max 53 characters. + /// + private static string ToHelmReleaseName(string name) + { + string s = name.ToLowerInvariant(); + s = System.Text.RegularExpressions.Regex.Replace(s, @"[^a-z0-9]+", "-"); + s = s.Trim('-'); + return s.Length > 53 ? s[..53].TrimEnd('-') : s; + } + + /// + /// Runs an external CLI process (helm, kubectl) and returns the combined + /// stdout + stderr. Exit code 0 → Success; non-zero → Failure. + /// + private static async Task RunCliAsync( + string program, string arguments, CancellationToken ct) + { + System.Diagnostics.ProcessStartInfo psi = new() + { + FileName = program, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using System.Diagnostics.Process process = new() { StartInfo = psi }; + + try + { + process.Start(); + + Task stdout = process.StandardOutput.ReadToEndAsync(ct); + Task stderr = process.StandardError.ReadToEndAsync(ct); + await process.WaitForExitAsync(ct); + + string combined = (await stdout).Trim(); + string err = (await stderr).Trim(); + if (!string.IsNullOrEmpty(err)) combined = string.IsNullOrEmpty(combined) ? err : combined + "\n" + err; + + return new HelmExecutionResult + { + Success = process.ExitCode == 0, + ExitCode = process.ExitCode, + Output = combined + }; + } + catch (Exception ex) + { + return new HelmExecutionResult + { + Success = false, + Output = $"Failed to run {program}: {ex.Message}" + }; + } + } } diff --git a/src/EntKube.Web/Services/KubernetesS3Proxy.cs b/src/EntKube.Web/Services/KubernetesS3Proxy.cs new file mode 100644 index 0000000..4e0fab0 --- /dev/null +++ b/src/EntKube.Web/Services/KubernetesS3Proxy.cs @@ -0,0 +1,268 @@ +using System.Text; +using Amazon.Runtime; +using Amazon.S3; +using k8s; +using k8s.Models; + +namespace EntKube.Web.Services; + +/// +/// Routes Amazon S3 SDK requests through the Kubernetes API server pod-proxy so that +/// cluster-internal MinIO pods are reachable from EntKube.Web. +/// +/// The core challenge: AWS Signature V4 includes the Host header. The K8s pod proxy +/// rewrites Host to podIP:port when forwarding to the pod, causing a signature mismatch +/// when the SDK signs for the API server hostname. +/// +/// Solution: sign with Host = podIP:port (what MinIO actually receives), then intercept +/// the request in KubernetesProxyHandler and rewrite the URL to the K8s pod-proxy path. +/// The signature remains valid; K8s routes based on the URL path, not the Host header. +/// +/// SDK signs request for: http://podIP:9000/bucket?cors +/// Handler rewrites to: https://k8s-api/api/v1/namespaces/{ns}/pods/{pod}:9000/proxy/bucket?cors +/// K8s forwards to pod: http://podIP:9000/bucket?cors (Host = podIP:9000) +/// MinIO validates: Host matches signed value ✓ +/// +public static class KubernetesS3Proxy +{ + // ── URL Parsing ── + + /// + /// Parses a cluster-internal service URL of the form + /// http(s)://svcName.namespace.svc.cluster.local[:port]. + /// Returns null if the URL doesn't match that pattern. + /// + public static (string Service, string Namespace, int Port)? ParseInternalServiceUrl(string url) + { + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri)) + return null; + + string[] parts = uri.Host.Split('.'); + if (parts.Length < 3 || !parts[2].Equals("svc", StringComparison.OrdinalIgnoreCase)) + return null; + + return (parts[0], parts[1], uri.Port > 0 ? uri.Port : (uri.Scheme == "https" ? 443 : 80)); + } + + // ── Open ── + + /// + /// Resolves a ready MinIO pod, builds a correctly-signed S3 client that routes + /// through the Kubernetes API server pod proxy, and returns both as a single + /// disposable. Returns null if the endpoint URL cannot be parsed. + /// + /// Usage: + /// using var proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, endpoint, key, secret); + /// var result = await proxied.S3.ListBucketsAsync(); + /// + public static async Task OpenAsync( + string kubeconfig, + string internalEndpoint, + string accessKey, + string secretKey, + string region = "us-east-1", + CancellationToken ct = default) + { + var parsed = ParseInternalServiceUrl(internalEndpoint); + if (parsed is null) + return null; + + using MemoryStream stream = new(Encoding.UTF8.GetBytes(kubeconfig)); + KubernetesClientConfiguration k8sCfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream); + Kubernetes k8s = new(k8sCfg); + + (string podName, string podIP) = await FindReadyPodAsync( + k8s, parsed.Value.Namespace, parsed.Value.Service, ct); + + // Use the K8s SERVICE proxy instead of the pod proxy. + // Pod proxy requires the API server to make a direct TCP connection to the pod IP, + // which fails with 503 in clusters where the control plane cannot reach pod IPs + // (common in managed/cloud K8s environments). + // Service proxy routes through kube-proxy/iptables — the same path as normal + // in-cluster traffic — and works regardless of control-plane ↔ pod connectivity. + // + // Look up the real service port (e.g. 443 for TLS MinIO, 9000 for plain HTTP). + // The service port in the URL must match a port the service actually exposes. + bool endpointTls = internalEndpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase); + (int svcPort, bool svcTls) = await GetServicePortAsync( + k8s, parsed.Value.Namespace, parsed.Value.Service, parsed.Value.Port, endpointTls, ct); + + string svcSchemePrefix = svcTls ? "https:" : ""; + + // K8s service proxy URL: the scheme prefix tells K8s whether to use TLS when + // connecting to the backend pod (InsecureSkipVerify=true for self-signed certs). + string proxyBase = k8s.BaseUri.ToString().TrimEnd('/') + + $"/api/v1/namespaces/{parsed.Value.Namespace}/services/{svcSchemePrefix}{parsed.Value.Service}:{svcPort}/proxy"; + + // AWS Signature V4 is signed for the host MinIO actually receives. + // K8s service proxy creates the backend request to podIP:podPort (from the Endpoints + // resource), so MinIO sees Host = podIP:podPort — same as pod proxy. + string signingScheme = svcTls ? "https" : "http"; + string signingBaseUrl = $"{signingScheme}://{podIP}:{parsed.Value.Port}"; + + // Custom handler intercepts SDK requests (addressed to podIP:port) and reroutes to K8s. + HttpClient proxyHttpClient = new(new KubernetesProxyHandler(k8s.HttpClient, proxyBase)); + + AmazonS3Config config = new() + { + ServiceURL = signingBaseUrl, + ForcePathStyle = true, + AuthenticationRegion = region, + UseHttp = !svcTls, // must match signingBaseUrl scheme + Timeout = TimeSpan.FromSeconds(30), + MaxErrorRetry = 0, + HttpClientFactory = new SingletonHttpClientFactory(proxyHttpClient) + }; + + AmazonS3Client s3 = new(new BasicAWSCredentials(accessKey, secretKey), config); + return new ProxiedS3Client(k8s, s3, proxyHttpClient); + } + + // ── Service Port Detection ── + + /// + /// Looks up the K8s service to find the port that maps to the given pod port. + /// Returns (servicePort=443, tls=true) for TLS MinIO (operator default), + /// or (servicePort=podPort, tls=false) if no 443 mapping is found. + /// + /// Whether the stored endpoint uses https:// — determines the + /// service proxy scheme prefix (https: tells K8s to use TLS for the backend connection). + /// Service port 443 does NOT imply TLS; the endpoint scheme is the source of truth. + private static async Task<(int ServicePort, bool Tls)> GetServicePortAsync( + Kubernetes k8s, string ns, string svc, int podPort, bool endpointTls, CancellationToken ct) + { + try + { + V1Service service = await k8s.CoreV1.ReadNamespacedServiceAsync(svc, ns, cancellationToken: ct); + + // Find the service port that targets our pod port. + V1ServicePort? match = service.Spec?.Ports?.FirstOrDefault(p => + p.TargetPort?.Value == podPort.ToString() + || (p.TargetPort?.Value == null && p.Port == podPort)); + + if (match is not null) + return (match.Port, endpointTls); // TLS from endpoint scheme, not service port + + // Fallback: use the first exposed port. + int first = service.Spec?.Ports?.FirstOrDefault()?.Port ?? podPort; + return (first, endpointTls); + } + catch + { + return (podPort, endpointTls); + } + } + + // ── Pod Discovery ── + + private static async Task<(string PodName, string PodIP)> FindReadyPodAsync( + Kubernetes k8s, string ns, string tenantName, CancellationToken ct) + { + // Primary: MinIO operator labels pods with v1.min.io/tenant=. + V1PodList pods = await k8s.CoreV1.ListNamespacedPodAsync( + ns, labelSelector: $"v1.min.io/tenant={tenantName}", cancellationToken: ct); + + var found = PickReadyPodWithIP(pods); + if (found is not null) + return found.Value; + + // Fallback: standalone MinIO or different label convention. + V1PodList all = await k8s.CoreV1.ListNamespacedPodAsync(ns, cancellationToken: ct); + found = PickReadyPodWithIP(all); + + return found ?? throw new InvalidOperationException( + $"No running MinIO pods with a pod IP found in namespace '{ns}'. " + + "Ensure MinIO is healthy before accessing bucket management."); + } + + private static (string PodName, string PodIP)? PickReadyPodWithIP(V1PodList podList) + { + foreach (V1Pod pod in podList.Items) + { + if (pod.Status?.Phase != "Running") continue; + if (string.IsNullOrEmpty(pod.Status.PodIP)) continue; + if (pod.Status.ContainerStatuses?.All(cs => cs.Ready) != true) continue; + return (pod.Metadata.Name, pod.Status.PodIP); + } + return null; + } + + // ── Proxy Handler ── + + /// + /// Intercepts Amazon SDK requests addressed to http://podIP:port/path and + /// rewrites the URL to the K8s pod-proxy endpoint before forwarding via the + /// K8s-authenticated HttpClient. All headers (including Authorization) are + /// preserved so AWS Signature V4 reaches MinIO intact. + /// + private sealed class KubernetesProxyHandler(HttpClient k8sClient, string proxyBase) : HttpMessageHandler + { + private readonly string _proxyBase = proxyBase.TrimEnd('/'); + + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + string pathAndQuery = request.RequestUri?.PathAndQuery ?? "/"; + string targetUrl = _proxyBase + pathAndQuery; + + HttpRequestMessage proxied = new(request.Method, targetUrl); + + // Copy all headers except Host. + // - Authorization (AWS Signature V4), X-Amz-Date, etc. must reach MinIO unchanged. + // - Host must NOT be copied: the AWS SDK sets Host=podIP:port for signing, but + // the K8s API server validates Host against its own hostname and closes the + // connection if it doesn't match ("unexpected EOF" / SSL error). + // K8s will set Host=podIP:port itself when forwarding to the pod, which is + // exactly what MinIO needs to validate the signature. + foreach (KeyValuePair> h in request.Headers) + { + if (string.Equals(h.Key, "Host", StringComparison.OrdinalIgnoreCase)) + continue; + proxied.Headers.TryAddWithoutValidation(h.Key, h.Value); + } + + if (request.Content is not null) + proxied.Content = request.Content; + + HttpResponseMessage response = await k8sClient.SendAsync(proxied, cancellationToken); + + // For 5xx errors from K8s, read the body so we can surface the real reason + // (e.g. "error trying to reach service: ...", certificate errors, RBAC, etc.) + // rather than a generic "ServiceUnavailable" from the Amazon SDK. + if ((int)response.StatusCode >= 500) + { + string body = await response.Content.ReadAsStringAsync(cancellationToken); + string snippet = body.Length > 600 ? body[..600] : body; + throw new InvalidOperationException( + $"K8s proxy returned {(int)response.StatusCode} for {targetUrl} — {snippet}"); + } + + return response; + } + } + + // ── Amazon SDK HttpClientFactory injection ── + + private sealed class SingletonHttpClientFactory(HttpClient client) : Amazon.Runtime.HttpClientFactory + { + public override HttpClient CreateHttpClient(IClientConfig clientConfig) => client; + public override bool UseSDKHttpClientCaching(IClientConfig clientConfig) => false; + public override bool DisposeHttpClientsAfterUse(IClientConfig clientConfig) => false; + } +} + +/// +/// A paired K8s client + proxied S3 client. Dispose to release all three resources +/// in the correct order (S3 → proxyHttpClient → K8s). +/// +public sealed class ProxiedS3Client(Kubernetes k8s, AmazonS3Client s3, HttpClient proxyHttpClient) : IDisposable +{ + public AmazonS3Client S3 { get; } = s3; + + public void Dispose() + { + S3.Dispose(); + proxyHttpClient.Dispose(); + k8s.Dispose(); + } +} diff --git a/src/EntKube.Web/Services/LokiService.cs b/src/EntKube.Web/Services/LokiService.cs new file mode 100644 index 0000000..e63adc8 --- /dev/null +++ b/src/EntKube.Web/Services/LokiService.cs @@ -0,0 +1,397 @@ +using System.Globalization; +using System.Text; +using System.Text.Json; +using EntKube.Web.Data; +using k8s; +using k8s.Models; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +public class LokiConfig +{ + public string Namespace { get; set; } = "monitoring"; + public string ServiceName { get; set; } = "loki"; + public int ServicePort { get; set; } = 3100; +} + +public class LokiLogStream +{ + public Dictionary Labels { get; set; } = new(); + public List Entries { get; set; } = []; +} + +public class LokiLogEntry +{ + public DateTime Timestamp { get; set; } + public string Line { get; set; } = ""; + public LogLevel DetectedLevel { get; set; } = LogLevel.None; +} + +public enum LogLevel { None, Debug, Info, Warn, Error, Fatal } + +/// +/// Queries Grafana Loki for logs via the Kubernetes API proxy endpoint, +/// using the same pattern as PrometheusService. Detects Loki automatically +/// by looking for a ClusterComponent with HelmChartName containing "loki". +/// +/// Also handles S3 chunk storage configuration: WriteStorageHelmValuesAsync +/// injects non-sensitive S3 values (region, bucket, endpoint) into HelmValues +/// and stores credentials as vault secrets for injection at install time. +/// +public class LokiService( + IDbContextFactory dbFactory, + ILogger logger, + VaultService vaultService, + StorageService storageService) +{ + public async Task>> GetNamespacesAsync( + Guid clusterId, CancellationToken ct = default) + { + var (info, error) = await ResolveLokiInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); + + return await WithLokiAsync>(info, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}/loki/api/v1/label/namespace/values", token); + return ParseLabelValues(json); + }, + $"loki namespaces {clusterId}", ct); + } + + public async Task>> GetPodsAsync( + Guid clusterId, string namespaceName, CancellationToken ct = default) + { + var (info, error) = await ResolveLokiInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); + + string query = string.IsNullOrEmpty(namespaceName) + ? "/loki/api/v1/label/pod/values" + : $"/loki/api/v1/label/pod/values?query={Uri.EscapeDataString($"{{namespace=\"{namespaceName}\"}}")}"; + + return await WithLokiAsync>(info, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}{query}", token); + return ParseLabelValues(json); + }, + $"loki pods {clusterId} ns={namespaceName}", ct); + } + + public async Task>> QueryRangeAsync( + Guid clusterId, + string namespaceName, + string? podName, + string? containerName, + string? textFilter, + DateTime from, + DateTime to, + int limit = 200, + CancellationToken ct = default) + { + var (info, error) = await ResolveLokiInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); + + string logQuery = BuildLogQL(namespaceName, podName, containerName, textFilter); + long fromNs = new DateTimeOffset(from.ToUniversalTime()).ToUnixTimeMilliseconds() * 1_000_000; + long toNs = new DateTimeOffset(to.ToUniversalTime()).ToUnixTimeMilliseconds() * 1_000_000; + + string url = $"/loki/api/v1/query_range?query={Uri.EscapeDataString(logQuery)}" + + $"&start={fromNs}&end={toNs}&limit={limit}&direction=backward"; + + return await WithLokiAsync>(info, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}{url}", token); + return ParseQueryRange(json); + }, + $"loki query {clusterId}", ct); + } + + public async Task IsAvailableAsync(Guid clusterId, CancellationToken ct = default) + { + var (info, _) = await ResolveLokiInfoAsync(clusterId, ct); + return info is not null; + } + + // ──────── Storage configuration ──────── + + /// + /// Injects S3-compatible storage configuration into the component's Helm values + /// and stores the access/secret key as vault secrets for injection at install time. + /// + /// Non-sensitive values (region, bucket names, endpoint, storage type) are written + /// directly to HelmValues. Credentials are stored encrypted in the vault and + /// injected by InjectSecretsIntoValuesAsync via the hidden catalog fields. + /// + public async Task WriteStorageHelmValuesAsync( + Guid tenantId, Guid clusterComponentId, Guid storageLinkId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent component = await db.ClusterComponents + .Include(c => c.Cluster) + .FirstOrDefaultAsync(c => c.Id == clusterComponentId && c.Cluster.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Component not found."); + + StorageLink link = await db.StorageLinks + .FirstOrDefaultAsync(s => s.Id == storageLinkId && s.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Storage link not found."); + + string region = link.Region ?? "us-east-1"; + string bucket = link.BucketName ?? ""; + + Dictionary s3Values = new() + { + ["loki.storage.type"] = "s3", + ["loki.storage.s3.region"] = region, + ["loki.storage.s3.s3ForcePathStyle"] = "true", + ["loki.storage.bucketNames.chunks"] = bucket, + ["loki.storage.bucketNames.ruler"] = bucket, + ["loki.storage.bucketNames.admin"] = bucket + }; + + if (!string.IsNullOrWhiteSpace(link.Endpoint)) + { + s3Values["loki.storage.s3.endpoint"] = link.Endpoint; + } + + component.HelmValues = YamlFormMerger.MergeFormValues(component.HelmValues ?? "", s3Values); + await db.SaveChangesAsync(ct); + + // Store credentials as vault secrets — injected at install time via hidden catalog fields + // loki-s3-access-key (→ loki.storage.s3.accessKeyId) and + // loki-s3-secret-key (→ loki.storage.s3.secretAccessKey). + await vaultService.InitializeVaultAsync(tenantId, ct); + (string accessKey, string secretKey) = await storageService.GetStoredCredentialsInternalAsync(tenantId, storageLinkId, ct); + + if (!string.IsNullOrEmpty(accessKey)) + { + await vaultService.SetComponentSecretAsync(tenantId, clusterComponentId, "loki-s3-access-key", accessKey, ct); + } + + if (!string.IsNullOrEmpty(secretKey)) + { + await vaultService.SetComponentSecretAsync(tenantId, clusterComponentId, "loki-s3-secret-key", secretKey, ct); + } + } + + // ──────── Internal ──────── + + private static string BuildLogQL(string ns, string? pod, string? container, string? text) + { + StringBuilder sb = new("{"); + sb.Append($"namespace=\"{ns}\""); + if (!string.IsNullOrEmpty(pod)) sb.Append($", pod=~\"{pod}.*\""); + if (!string.IsNullOrEmpty(container)) sb.Append($", container=\"{container}\""); + sb.Append('}'); + + if (!string.IsNullOrEmpty(text)) + sb.Append($" |= \"{text.Replace("\"", "\\\"")}\""); + + return sb.ToString(); + } + + private static List ParseLabelValues(string json) + { + try + { + using JsonDocument doc = JsonDocument.Parse(json); + if (doc.RootElement.GetProperty("status").GetString() != "success") return []; + return [.. doc.RootElement.GetProperty("data") + .EnumerateArray() + .Select(e => e.GetString() ?? "") + .Where(s => !string.IsNullOrEmpty(s)) + .OrderBy(s => s)]; + } + catch { return []; } + } + + private static List ParseQueryRange(string json) + { + List results = []; + try + { + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + + if (root.GetProperty("status").GetString() != "success") return results; + + JsonElement data = root.GetProperty("data"); + if (!data.GetProperty("resultType").GetString()!.Equals("streams", StringComparison.OrdinalIgnoreCase)) + return results; + + foreach (JsonElement stream in data.GetProperty("result").EnumerateArray()) + { + LokiLogStream ls = new(); + + if (stream.TryGetProperty("stream", out JsonElement labels)) + foreach (JsonProperty p in labels.EnumerateObject()) + ls.Labels[p.Name] = p.Value.GetString() ?? ""; + + if (stream.TryGetProperty("values", out JsonElement values)) + foreach (JsonElement entry in values.EnumerateArray()) + { + if (entry.GetArrayLength() < 2) continue; + string tsNs = entry[0].GetString() ?? "0"; + string line = entry[1].GetString() ?? ""; + + DateTime ts = long.TryParse(tsNs, out long ns) + ? DateTimeOffset.FromUnixTimeMilliseconds(ns / 1_000_000).UtcDateTime + : DateTime.UtcNow; + + ls.Entries.Add(new LokiLogEntry + { + Timestamp = ts, + Line = line, + DetectedLevel = DetectLevel(line) + }); + } + + if (ls.Entries.Count > 0) results.Add(ls); + } + } + catch { } + return results; + } + + private static LogLevel DetectLevel(string line) + { + if (line.Contains("FATAL", StringComparison.OrdinalIgnoreCase) || + line.Contains("CRITICAL", StringComparison.OrdinalIgnoreCase)) return LogLevel.Fatal; + if (line.Contains("ERROR", StringComparison.OrdinalIgnoreCase) || + line.Contains(" ERR ", StringComparison.OrdinalIgnoreCase)) return LogLevel.Error; + if (line.Contains("WARN", StringComparison.OrdinalIgnoreCase)) return LogLevel.Warn; + if (line.Contains("DEBUG", StringComparison.OrdinalIgnoreCase)) return LogLevel.Debug; + if (line.Contains("INFO", StringComparison.OrdinalIgnoreCase)) return LogLevel.Info; + return LogLevel.None; + } + + private sealed record ResolvedLokiInfo(string Kubeconfig, LokiConfig Config); + + private async Task<(ResolvedLokiInfo? Info, string? Error)> ResolveLokiInfoAsync( + Guid clusterId, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster? cluster = await db.KubernetesClusters + .Include(c => c.Components) + .FirstOrDefaultAsync(c => c.Id == clusterId, ct); + + if (cluster is null) return (null, "Cluster not found."); + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) return (null, "No kubeconfig configured."); + + ClusterComponent? lokiComponent = cluster.Components.FirstOrDefault(c => + (c.HelmChartName ?? "").Contains("loki", StringComparison.OrdinalIgnoreCase) || + c.Name.Contains("loki", StringComparison.OrdinalIgnoreCase)); + + if (lokiComponent is null) return (null, "No Loki component found on this cluster."); + if (lokiComponent.Status != ComponentStatus.Installed) return (null, "Loki is not installed."); + + LokiConfig config = BuildLokiConfig(lokiComponent); + return (new ResolvedLokiInfo(cluster.Kubeconfig, config), null); + } + + private static LokiConfig BuildLokiConfig(ClusterComponent component) + { + string ns = component.Namespace ?? "monitoring"; + string releaseName = component.ReleaseName ?? component.Name; + + LokiConfig derived = new() + { + Namespace = ns, + ServiceName = releaseName.Contains("loki", StringComparison.OrdinalIgnoreCase) + ? releaseName : $"{releaseName}-loki" + }; + + if (string.IsNullOrWhiteSpace(component.Configuration)) return derived; + try + { + LokiConfig? explicit_ = JsonSerializer.Deserialize(component.Configuration, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + if (explicit_ is null) return derived; + if (!string.IsNullOrWhiteSpace(explicit_.Namespace)) derived.Namespace = explicit_.Namespace; + if (!string.IsNullOrWhiteSpace(explicit_.ServiceName)) derived.ServiceName = explicit_.ServiceName; + if (explicit_.ServicePort != 3100) derived.ServicePort = explicit_.ServicePort; + } + catch { } + return derived; + } + + private async Task> WithLokiAsync( + ResolvedLokiInfo info, + Func> action, + string logContext, + CancellationToken ct) + { + try + { + using MemoryStream stream = new(Encoding.UTF8.GetBytes(info.Kubeconfig)); + KubernetesClientConfiguration k8sConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream); + using Kubernetes k8s = new(k8sConfig); + + V1EndpointAddress? addr = await FindLokiEndpointAsync(k8s, info.Config, ct); + if (addr?.TargetRef is null) + throw new InvalidOperationException( + $"No ready pods for Loki service {info.Config.ServiceName} in {info.Config.Namespace}."); + + string podName = addr.TargetRef.Name; + string podNs = addr.TargetRef.NamespaceProperty ?? info.Config.Namespace; + string baseUrl = k8s.BaseUri.ToString().TrimEnd('/') + + $"/api/v1/namespaces/{podNs}/pods/{podName}:{info.Config.ServicePort}/proxy"; + + logger.LogDebug("Loki proxy → pod {Pod} ({PodNs}) at {BaseUrl}", podName, podNs, baseUrl); + + T result = await action(k8s.HttpClient, baseUrl, ct); + return KubernetesOperationResult.Success(result); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Loki query failed ({Context})", logContext); + return KubernetesOperationResult.Failure(ex.Message); + } + } + + private async Task FindLokiEndpointAsync( + Kubernetes k8s, LokiConfig config, CancellationToken ct) + { + try + { + V1Endpoints ep = await k8s.CoreV1.ReadNamespacedEndpointsAsync( + config.ServiceName, config.Namespace, cancellationToken: ct); + V1EndpointAddress? direct = ReadyPodAddress(ep, config.ServicePort); + if (direct is not null) return direct; + } + catch (k8s.Autorest.HttpOperationException ex) + when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound) { } + + // Fallback: scan namespace for anything named "loki" + V1EndpointsList all = await k8s.CoreV1.ListNamespacedEndpointsAsync( + config.Namespace, cancellationToken: ct); + + foreach (V1Endpoints candidate in all.Items.OrderBy(e => e.Metadata.Name)) + { + if (!candidate.Metadata.Name.Contains("loki", StringComparison.OrdinalIgnoreCase)) continue; + V1EndpointAddress? addr = ReadyPodAddress(candidate, config.ServicePort); + if (addr is null) continue; + logger.LogInformation("Auto-discovered Loki endpoint {Found}", candidate.Metadata.Name); + return addr; + } + return null; + } + + private static V1EndpointAddress? ReadyPodAddress(V1Endpoints ep, int port) + { + if (ep.Subsets is null) return null; + foreach (V1EndpointSubset subset in ep.Subsets) + { + bool portMatches = subset.Ports?.Any(p => p.Port == port) ?? true; + if (!portMatches) continue; + V1EndpointAddress? addr = (subset.Addresses ?? []) + .FirstOrDefault(a => a.TargetRef?.Kind == "Pod"); + if (addr is not null) return addr; + } + return null; + } +} diff --git a/src/EntKube.Web/Services/MongoService.cs b/src/EntKube.Web/Services/MongoService.cs index 28d029e..71873bc 100644 --- a/src/EntKube.Web/Services/MongoService.cs +++ b/src/EntKube.Web/Services/MongoService.cs @@ -43,6 +43,8 @@ public class MongoService( Guid? storageLinkId, string? backupSchedule, int retentionDays = 30, + int maxBackups = 20, + string mongoVersion = "8.3.2", CancellationToken ct = default) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -52,7 +54,8 @@ public class MongoService( bool operatorInstalled = await db.ClusterComponents .AnyAsync(c => c.ClusterId == kubernetesClusterId && (c.Name == "mongodb-community-operator" || c.Name == "mongodb-operator" - || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator") + || c.ReleaseName == "mongodb-community-operator" || c.ReleaseName == "mongodb-operator" + || c.HelmChartName == "community-operator") && c.Status == ComponentStatus.Installed, ct); if (!operatorInstalled) @@ -85,12 +88,13 @@ public class MongoService( KubernetesClusterId = kubernetesClusterId, Name = name, Namespace = ns, - MongoVersion = "8.0.4", + MongoVersion = string.IsNullOrWhiteSpace(mongoVersion) ? "8.3.2" : mongoVersion.Trim(), Members = members, StorageSize = storageSize, StorageLinkId = storageLinkId, BackupSchedule = backupSchedule, RetentionDays = retentionDays, + MaxBackups = maxBackups, Status = MongoClusterStatus.Creating }; @@ -120,7 +124,7 @@ public class MongoService( adminK8sSecretName, ns, ct); string adminSecretManifest = BuildAdminSecretManifest(name, ns, adminPassword); - string clusterManifest = BuildClusterManifest(mongoCluster, storageLink, s3SecretName); + string clusterManifest = BuildClusterManifest(mongoCluster); await k8sFactory.EnsureNamespaceAsync(ns, k8sCluster.Kubeconfig!, ct); @@ -148,6 +152,29 @@ public class MongoService( return mongoCluster; } + /// + /// Removes an externally-registered MongoDB cluster from EntKube without touching + /// any Kubernetes resources. Use when the cluster was registered (not created) and + /// the user wants to remove it from EntKube only. + /// + public async Task UnregisterClusterAsync(Guid tenantId, Guid mongoClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.Databases) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + foreach (MongoDatabase database in mongo.Databases) + await DeleteDatabaseSecretsAsync(database.Id, ct); + + await DeleteClusterSecretsAsync(mongo.Id, ct); + + db.MongoClusters.Remove(mongo); + await db.SaveChangesAsync(ct); + } + /// /// Deletes a managed MongoDB cluster from Kubernetes and removes all associated /// records (databases, backups, vault secrets). @@ -202,6 +229,103 @@ public class MongoService( await db.SaveChangesAsync(ct); } + /// + /// Updates backup settings (schedule, retention, max backup count) for an existing cluster. + /// Saves the values to the database and re-applies or removes the scheduled backup CronJob. + /// + public async Task UpdateBackupSettingsAsync( + Guid tenantId, Guid mongoClusterId, + string? schedule, int retentionDays, int maxBackups, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .Include(c => c.StorageLink) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + mongo.BackupSchedule = string.IsNullOrWhiteSpace(schedule) ? null : schedule.Trim(); + mongo.RetentionDays = retentionDays; + mongo.MaxBackups = maxBackups > 0 ? maxBackups : 20; + await db.SaveChangesAsync(ct); + + string? s3SecretName = mongo.StorageLinkId.HasValue ? $"{mongo.Name}-s3-credentials" : null; + + if (!string.IsNullOrWhiteSpace(mongo.BackupSchedule) && mongo.StorageLink is not null && s3SecretName is not null) + { + string cronManifest = BuildScheduledBackupCronJobManifest(mongo, mongo.StorageLink, s3SecretName); + await k8sFactory.ApplyManifestAsync(cronManifest, mongo.KubernetesCluster.Kubeconfig!, ct); + } + else if (mongo.StorageLink is not null) + { + try + { + await k8sFactory.DeleteManifestAsync( + "cronjob", $"{mongo.Name}-scheduled-backup", mongo.Namespace, + mongo.KubernetesCluster.Kubeconfig!, ct); + } + catch (Exception) + { + // CronJob may not exist yet — ignore. + } + } + } + + /// + /// Removes the oldest completed backup Jobs from Kubernetes beyond the MaxBackups limit. + /// Does not affect backup data in S3 — use S3 lifecycle rules for that. + /// + public async Task CleanupOldBackupsAsync(Guid tenantId, Guid mongoClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + int limit = mongo.MaxBackups > 0 ? mongo.MaxBackups : 20; + + List backups = await FetchMongoBackupsFromK8sAsync(mongo, ct); + + List completed = backups + .Where(b => b.Status == MongoBackupStatus.Completed) + .OrderByDescending(b => b.StartedAt) + .ToList(); + + if (completed.Count <= limit) + return 0; + + List toDelete = completed.Skip(limit).ToList(); + int deleted = 0; + + foreach (MongoBackup backup in toDelete) + { + try + { + await k8sFactory.DeleteManifestAsync("Job", backup.Name, mongo.Namespace, mongo.KubernetesCluster.Kubeconfig!, ct); + + MongoBackup? dbRecord = await db.MongoBackups + .FirstOrDefaultAsync(b => b.MongoClusterId == mongoClusterId && b.Name == backup.Name, ct); + if (dbRecord is not null) + db.MongoBackups.Remove(dbRecord); + + deleted++; + } + catch (Exception) + { + // Continue with remaining backups if one deletion fails. + } + } + + if (deleted > 0) + await db.SaveChangesAsync(ct); + + return deleted; + } + /// /// Upgrades a MongoDB cluster to a new version. Updates spec.version in the CRD — /// the Community Operator performs a rolling upgrade of replica set members. @@ -221,16 +345,110 @@ public class MongoService( mongo.Status = MongoClusterStatus.Upgrading; await db.SaveChangesAsync(ct); - string? s3SecretName = mongo.StorageLinkId.HasValue ? $"{mongo.Name}-s3-credentials" : null; - string manifest = BuildClusterManifest(mongo, mongo.StorageLink, s3SecretName); - - await k8sFactory.ApplyManifestAsync(manifest, mongo.KubernetesCluster.Kubeconfig!, ct); - - // Re-apply the CronJob so the mongodump image version stays in sync. - if (!string.IsNullOrEmpty(mongo.BackupSchedule) && mongo.StorageLink is not null && s3SecretName is not null) + if (mongo.IsExternal) { - string cronManifest = BuildScheduledBackupCronJobManifest(mongo, mongo.StorageLink, s3SecretName); - await k8sFactory.ApplyManifestAsync(cronManifest, mongo.KubernetesCluster.Kubeconfig!, ct); + // External clusters: patch only spec.version to avoid clobbering existing spec.users and other config. + string versionFull = targetVersion.Count(c => c == '.') < 2 + ? targetVersion + ".0" + : targetVersion; + + string jsonPatch = $"{{\"spec\":{{\"version\":\"{versionFull}\"}}}}"; + await k8sFactory.PatchJsonAsync( + "mongodbcommunity", mongo.Name, mongo.Namespace, + jsonPatch, mongo.KubernetesCluster.Kubeconfig!, ct); + } + else + { + string? s3SecretName = mongo.StorageLinkId.HasValue ? $"{mongo.Name}-s3-credentials" : null; + string manifest = BuildClusterManifest(mongo); + + await k8sFactory.ApplyManifestAsync(manifest, mongo.KubernetesCluster.Kubeconfig!, ct); + + // Re-apply the CronJob so the mongodump image version stays in sync. + if (!string.IsNullOrEmpty(mongo.BackupSchedule) && mongo.StorageLink is not null && s3SecretName is not null) + { + string cronManifest = BuildScheduledBackupCronJobManifest(mongo, mongo.StorageLink, s3SecretName); + await k8sFactory.ApplyManifestAsync(cronManifest, mongo.KubernetesCluster.Kubeconfig!, ct); + } + } + } + + /// + /// Resizes a running MongoDB cluster. Two independent operations can be triggered: + /// + /// CPU/Memory — updates the DB record and re-applies the MongoDBCommunity manifest. + /// The Community Operator propagates the change to the StatefulSet pod template and + /// performs a rolling restart, so the cluster stays available throughout. + /// + /// Storage — patches each PVC directly via the Kubernetes API (bypasses the immutable + /// StatefulSet volumeClaimTemplate). Requires the StorageClass to have + /// allowVolumeExpansion: true. The kubelet expands the filesystem without restarting + /// the pod when online expansion is supported; otherwise, a pod restart is required. + /// StorageSize on the DB record is updated on success. + /// + /// Either set of changes can be applied independently or together. + /// + public async Task ResizeClusterAsync( + Guid tenantId, + Guid mongoClusterId, + string? storageSize, + string? cpuRequest, + string? cpuLimit, + string? memoryRequest, + string? memoryLimit, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .Include(c => c.StorageLink) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + bool resourcesChanged = cpuRequest != mongo.CpuRequest + || cpuLimit != mongo.CpuLimit + || memoryRequest != mongo.MemoryRequest + || memoryLimit != mongo.MemoryLimit; + + bool storageChanged = !string.IsNullOrWhiteSpace(storageSize) + && storageSize.Trim() != mongo.StorageSize; + + // ── CPU / Memory ────────────────────────────────────────────────────────── + if (resourcesChanged) + { + mongo.CpuRequest = string.IsNullOrWhiteSpace(cpuRequest) ? null : cpuRequest.Trim(); + mongo.CpuLimit = string.IsNullOrWhiteSpace(cpuLimit) ? null : cpuLimit.Trim(); + mongo.MemoryRequest = string.IsNullOrWhiteSpace(memoryRequest) ? null : memoryRequest.Trim(); + mongo.MemoryLimit = string.IsNullOrWhiteSpace(memoryLimit) ? null : memoryLimit.Trim(); + await db.SaveChangesAsync(ct); + + // Re-apply the full manifest — the operator performs a rolling restart to + // pick up the new resource requests/limits on the StatefulSet pod template. + string manifest = BuildClusterManifest(mongo); + await k8sFactory.ApplyManifestAsync(manifest, mongo.KubernetesCluster.Kubeconfig!, ct); + } + + // ── Storage ─────────────────────────────────────────────────────────────── + if (storageChanged) + { + string newSize = storageSize!.Trim(); + + // Patch each PVC individually — StatefulSet volumeClaimTemplates are immutable + // so we cannot go through the CRD. The kubelet expands the filesystem in-place + // when the StorageClass supports online expansion. + string patch = $"{{\"spec\":{{\"resources\":{{\"requests\":{{\"storage\":\"{newSize}\"}}}}}}}}"; + + for (int i = 0; i < mongo.Members; i++) + { + string pvcName = $"data-volume-{mongo.Name}-{i}"; + await k8sFactory.PatchJsonAsync( + "pvc", pvcName, mongo.Namespace, patch, + mongo.KubernetesCluster.Kubeconfig!, ct); + } + + mongo.StorageSize = newSize; + await db.SaveChangesAsync(ct); } } @@ -280,6 +498,10 @@ public class MongoService( await EnsureStorageSecretsInK8sAsync( tenantId, storageLink, s3SecretName, mongo.Namespace, mongo.KubernetesCluster.Kubeconfig!, ct); + // Both managed and external clusters use the same backup manifest: the Job connects + // to {name}-svc (internal service) using the {name}-admin-password K8s Secret. + // For external clusters the user stores their admin credentials via UpdateExternalClusterSettingsAsync, + // which writes that same secret — no external URI required. string manifest = BuildBackupManifest(backupName, mongo, storageLink, s3SecretName); await k8sFactory.ApplyManifestAsync(manifest, mongo.KubernetesCluster.Kubeconfig!, ct); @@ -339,6 +561,130 @@ public class MongoService( return restoreRecord; } + /// + /// Restores a database from a MongoDB Atlas cluster into a managed MongoDB cluster. + /// Creates a Kubernetes Job with two stages: an init container runs mongodump + /// against the Atlas connection string, then the main container runs mongorestore + /// into the target replica set. Atlas credentials are stored as a short-lived + /// K8s Secret that is cleaned up automatically via ttlSecondsAfterFinished. + /// + public async Task RestoreFromAtlasAsync( + Guid tenantId, + Guid mongoClusterId, + string atlasUri, + string sourceDatabase, + string targetDatabase, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + if (string.IsNullOrWhiteSpace(atlasUri)) + { + throw new ArgumentException("Atlas connection string is required."); + } + + string jobName = $"{mongo.Name}-atlas-restore-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + string atlasSecretName = $"{jobName}-atlas-uri"; + + // Store the Atlas URI in a short-lived K8s Secret so it is not embedded in the Job manifest. + string atlasSecretManifest = BuildAtlasUriSecret(atlasSecretName, mongo.Namespace, atlasUri); + await k8sFactory.ApplyManifestAsync(atlasSecretManifest, mongo.KubernetesCluster.Kubeconfig!, ct); + + string manifest = BuildAtlasRestoreManifest( + jobName, mongo, atlasSecretName, sourceDatabase, targetDatabase); + await k8sFactory.ApplyManifestAsync(manifest, mongo.KubernetesCluster.Kubeconfig!, ct); + } + + private static string BuildAtlasUriSecret(string secretName, string ns, string atlasUri) + { + string encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(atlasUri)); + StringBuilder sb = new(); + sb.AppendLine("apiVersion: v1"); + sb.AppendLine("kind: Secret"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {secretName}"); + sb.AppendLine($" namespace: {ns}"); + sb.AppendLine("type: Opaque"); + sb.AppendLine("data:"); + sb.AppendLine($" uri: {encoded}"); + return sb.ToString(); + } + + private static string BuildAtlasRestoreManifest( + string jobName, MongoCluster mongo, + string atlasSecretName, string sourceDatabase, string targetDatabase) + { + // The dump container connects to Atlas and must use a modern image: Ubuntu 22.04 + OpenSSL 3.x + // (mongo 7.0+). Atlas enforces TLS 1.2+ with cipher suites that older OpenSSL stacks + // cannot negotiate, producing "remote error: tls: internal error" on the server side. + // mongodump tools are backwards-compatible — a 7.0 client can dump any Atlas cluster version. + const string atlasDumpImage = "mongo:7.0"; + + // The restore container connects to the local in-cluster replica set, so it can match + // the target cluster's version for maximum protocol compatibility. + string mongoImage = MongoImage(mongo.MongoVersion); + + string targetHost = $"{mongo.Name}-svc.{mongo.Namespace}.svc.cluster.local"; + // socketTimeoutMS=0 disables per-operation socket timeouts so slow large-collection + // writes don't cause the driver to RST the connection mid-write. + // heartbeatFrequencyMS keeps the connection alive through NAT idle-timeout windows. + string targetUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{targetHost}:27017/?authSource=admin&replicaSet={mongo.Name}&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=10000"; + + StringBuilder sb = new(); + sb.AppendLine("apiVersion: batch/v1"); + sb.AppendLine("kind: Job"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {jobName}"); + sb.AppendLine($" namespace: {mongo.Namespace}"); + sb.AppendLine(" labels:"); + sb.AppendLine($" entkube.io/mongo-cluster: {mongo.Name}"); + sb.AppendLine(" entkube.io/restore-source: atlas"); + sb.AppendLine("spec:"); + sb.AppendLine(" backoffLimit: 0"); + sb.AppendLine(" ttlSecondsAfterFinished: 86400"); + sb.AppendLine(" template:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" restartPolicy: Never"); + sb.AppendLine(" volumes:"); + sb.AppendLine(" - name: dump-data"); + sb.AppendLine(" emptyDir: {}"); + sb.AppendLine(" initContainers:"); + sb.AppendLine(" - name: dump-from-atlas"); + sb.AppendLine($" image: {atlasDumpImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongodump --uri=\\\"$ATLAS_URI\\\" --db={sourceDatabase} --numParallelCollections=1 --gzip --archive=/dump/dump.archive\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: ATLAS_URI"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {atlasSecretName}"); + sb.AppendLine(" key: uri"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: dump-data"); + sb.AppendLine(" mountPath: /dump"); + sb.AppendLine(" containers:"); + sb.AppendLine(" - name: restore-to-cluster"); + sb.AppendLine($" image: {mongoImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{targetUri}\\\" --gzip --archive=/dump/dump.archive --nsFrom='{sourceDatabase}.*' --nsTo='{targetDatabase}.*' --drop --numInsertionWorkersPerCollection=1 --batchSize=100\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: MONGO_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {mongo.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: dump-data"); + sb.AppendLine(" mountPath: /dump"); + + return sb.ToString(); + } + /// /// Creates a new MongoDB cluster from an existing backup. The new cluster is /// provisioned fresh (new admin credentials, RBAC, StatefulSet), then a restore @@ -373,7 +719,8 @@ public class MongoService( MongoCluster newCluster = await CreateClusterAsync( tenantId, source.KubernetesClusterId, newName, source.Namespace, source.Members, source.StorageSize, source.StorageLinkId, - source.BackupSchedule, source.RetentionDays, ct); + source.BackupSchedule, source.RetentionDays, source.MaxBackups, + source.MongoVersion, ct); string restoreName = $"{newName}-restore-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; string s3SecretName = $"{newName}-s3-credentials"; @@ -554,6 +901,260 @@ public class MongoService( await db.SaveChangesAsync(ct); } + /// + /// Removes a database from EntKube without running any MongoDB commands. + /// Use when the cluster is external and the database should only be untracked, + /// not actually dropped from the server. + /// + public async Task UnregisterDatabaseAsync(Guid tenantId, Guid mongoClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + MongoDatabase database = await db.MongoDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.MongoClusterId == mongo.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + await DeleteDatabaseSecretsAsync(database.Id, ct); + + db.MongoDatabases.Remove(database); + await db.SaveChangesAsync(ct); + } + + // ──────── Database Migration ──────── + + /// + /// Migrates a single database from one managed MongoDB cluster to another by running a + /// two-container Kubernetes Job on the source cluster's K8s environment. + /// The init container dumps the source database via mongodump --db, the main container + /// restores it to the target cluster with mongorestore --db. + /// + /// Both clusters must be reachable via their internal service endpoints from the K8s + /// cluster where the Job runs (i.e. both clusters should be on the same K8s cluster, + /// or the source cluster's K8s must have network access to the target's service). + /// + public async Task MigrateDatabaseAsync( + Guid tenantId, + Guid sourceClusterId, + Guid sourceDatabaseId, + Guid targetClusterId, + string targetDatabaseName, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster source = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == sourceClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Source MongoDB cluster not found."); + + MongoDatabase sourceDb = await db.MongoDatabases + .FirstOrDefaultAsync(d => d.Id == sourceDatabaseId && d.MongoClusterId == sourceClusterId, ct) + ?? throw new InvalidOperationException("Source database not found."); + + MongoCluster target = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == targetClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Target MongoDB cluster not found."); + + if (string.IsNullOrWhiteSpace(targetDatabaseName)) + targetDatabaseName = sourceDb.Name; + + string jobName = $"{sourceDb.Name}-to-{target.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + string manifest = BuildMigrateDatabaseManifest(jobName, source, sourceDb.Name, target, targetDatabaseName.Trim()); + + // Run the Job on the source cluster's K8s so the source service is always reachable. + await k8sFactory.ApplyManifestAsync(manifest, source.KubernetesCluster.Kubeconfig!, ct); + } + + private static string BuildMigrateDatabaseManifest( + string jobName, MongoCluster source, string sourceDbName, + MongoCluster target, string targetDbName) + { + string sourceImage = MongoImage(source.MongoVersion); + string targetImage = MongoImage(target.MongoVersion); + + string sourceHost = $"{source.Name}-svc.{source.Namespace}.svc.cluster.local"; + string sourceUri = $"mongodb://admin:${{SOURCE_ADMIN_PASSWORD}}@{sourceHost}:27017/?authSource=admin&replicaSet={source.Name}&readPreference=secondaryPreferred&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; + + string targetHost = $"{target.Name}-svc.{target.Namespace}.svc.cluster.local"; + string targetUri = $"mongodb://admin:${{TARGET_ADMIN_PASSWORD}}@{targetHost}:27017/?authSource=admin&replicaSet={target.Name}&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; + + StringBuilder sb = new(); + sb.AppendLine("apiVersion: batch/v1"); + sb.AppendLine("kind: Job"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {jobName}"); + sb.AppendLine($" namespace: {source.Namespace}"); + sb.AppendLine(" labels:"); + sb.AppendLine($" entkube.io/mongo-cluster: {source.Name}"); + sb.AppendLine($" entkube.io/db-migration-target: {target.Name}"); + // Wait for the source to be ready — pings every 10 s until mongosh succeeds. + // Catches the case where the cluster is still settling after a rolling upgrade. + string sourceWaitUri = $"mongodb://admin:${{SOURCE_ADMIN_PASSWORD}}@{sourceHost}:27017/?authSource=admin&replicaSet={source.Name}&connectTimeoutMS=10000&serverSelectionTimeoutMS=15000"; + + sb.AppendLine("spec:"); + sb.AppendLine(" backoffLimit: 2"); + sb.AppendLine(" ttlSecondsAfterFinished: 86400"); + sb.AppendLine(" template:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" restartPolicy: Never"); + sb.AppendLine(" volumes:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" emptyDir: {}"); + sb.AppendLine(" initContainers:"); + sb.AppendLine(" - name: wait-for-source"); + sb.AppendLine($" image: {sourceImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"until mongosh \\\"{sourceWaitUri}\\\" --username admin --password \\\"${{SOURCE_ADMIN_PASSWORD}}\\\" --authenticationDatabase admin --eval 'db.adminCommand({{ping:1}})' --quiet; do echo 'waiting for source...'; sleep 10; done\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: SOURCE_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {source.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" - name: dump-source-db"); + sb.AppendLine($" image: {sourceImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongodump --uri=\\\"{sourceUri}\\\" --db='{sourceDbName}' --numParallelCollections=1 --gzip --archive=/backup/dump.archive\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: SOURCE_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {source.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" mountPath: /backup"); + sb.AppendLine(" containers:"); + sb.AppendLine(" - name: restore-target-db"); + sb.AppendLine($" image: {targetImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{targetUri}\\\" --db='{targetDbName}' --gzip --archive=/backup/dump.archive --drop --nsFrom='{sourceDbName}.*' --nsTo='{targetDbName}.*'\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: TARGET_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {target.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" mountPath: /backup"); + return sb.ToString(); + } + + // ──────── Database Credentials ──────── + + /// + /// Returns the decrypted connection credentials for a MongoDB database from the vault. + /// Keys: HOST, PORT, DATABASE, USERNAME, PASSWORD, CONNECTION_STRING. + /// + public async Task> GetDatabaseCredentialsAsync( + Guid tenantId, Guid databaseId, CancellationToken ct = default) + { + List secrets = await vaultService.GetMongoDatabaseSecretsAsync(tenantId, databaseId, ct); + + Dictionary credentials = []; + foreach (VaultSecret secret in secrets) + { + string? value = await vaultService.GetSecretValueByIdAsync(secret.Id, ct); + if (value is not null) + credentials[secret.Name] = value; + } + return credentials; + } + + /// + /// Pushes the database's stored credentials to Kubernetes as a Secret in the cluster + /// namespace. Creates or updates {cluster}-{database}-credentials in the cluster's + /// namespace so applications can mount it as env vars. + /// + public async Task SyncDatabaseCredentialsToK8sAsync( + Guid tenantId, Guid mongoClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + MongoDatabase database = await db.MongoDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.MongoClusterId == mongo.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + Dictionary credentials = await GetDatabaseCredentialsAsync(tenantId, databaseId, ct); + if (credentials.Count == 0) + throw new InvalidOperationException("No credentials found in the vault for this database."); + + string secretName = $"{mongo.Name}-{database.Name}-credentials"; + string kubeconfig = mongo.KubernetesCluster.Kubeconfig!; + + StringBuilder sb = new(); + sb.AppendLine("apiVersion: v1"); + sb.AppendLine("kind: Secret"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {secretName}"); + sb.AppendLine($" namespace: {mongo.Namespace}"); + sb.AppendLine("type: Opaque"); + sb.AppendLine("stringData:"); + foreach (KeyValuePair kv in credentials) + sb.AppendLine($" {kv.Key}: \"{kv.Value.Replace("\"", "\\\"")}\""); + + await k8sFactory.ApplyManifestAsync(sb.ToString(), kubeconfig, ct); + + // Mark vault secrets as synced. + List vaultSecrets = await vaultService.GetMongoDatabaseSecretsAsync(tenantId, databaseId, ct); + foreach (VaultSecret secret in vaultSecrets) + await vaultService.ConfigureKubernetesSyncAsync(secret.Id, true, secretName, mongo.Namespace, ct); + } + + /// + /// Generates a new password for a MongoDB database user, updates it in MongoDB via + /// mongosh exec on the primary pod, and stores the new credentials in the vault. + /// + public async Task RotateDatabasePasswordAsync( + Guid tenantId, Guid mongoClusterId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("MongoDB cluster not found."); + + MongoDatabase database = await db.MongoDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.MongoClusterId == mongo.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + string? adminPassword = await vaultService.GetMongoClusterSecretValueAsync( + tenantId, mongo.Id, "admin-password", ct) + ?? throw new InvalidOperationException("Admin password not found in vault."); + + string newPassword = GeneratePassword(); + + // Update the user password via mongosh on the primary pod. + // Double-braces escape the MongoDB object literal from C# string interpolation. + string script = $"db.getSiblingDB('{database.Name}').updateUser('{database.Owner}', {{pwd: '{newPassword}'}});"; + + await k8sFactory.ExecuteMongoAsync( + mongo.Name, mongo.Namespace, script, + mongo.KubernetesCluster.Kubeconfig!, + username: "admin", password: adminPassword, ct: ct); + + // Store updated credentials in vault. + string host = $"{mongo.Name}-svc.{mongo.Namespace}.svc.cluster.local"; + string connectionString = + $"mongodb://{database.Owner}:{newPassword}@{host}:27017/{database.Name}?authSource={database.Name}&replicaSet={mongo.Name}"; + string k8sSecretName = $"{mongo.Name}-{database.Name}-credentials"; + + await StoreDatabaseSecretAsync(tenantId, database.Id, "PASSWORD", newPassword, k8sSecretName, mongo.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "CONNECTION_STRING", connectionString, k8sSecretName, mongo.Namespace, ct); + } + // ──────── Queries ──────── /// @@ -574,6 +1175,258 @@ public class MongoService( .ToListAsync(ct); } + /// + /// Registers an existing MongoDB cluster that was deployed outside EntKube. + /// Creates a MongoCluster record pointing to the live resource without applying + /// any manifest. The cluster must already be running on the target K8s cluster. + /// + public async Task RegisterExistingClusterAsync( + Guid tenantId, + Guid kubernetesClusterId, + string name, + string ns, + string? externalUri = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongoCluster = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KubernetesClusterId = kubernetesClusterId, + Name = name, + Namespace = ns, + MongoVersion = "unknown", + Members = 1, + StorageSize = "unknown", + Status = MongoClusterStatus.Running, + IsExternal = true, + ExternalUri = string.IsNullOrWhiteSpace(externalUri) ? null : externalUri.Trim() + }; + + db.MongoClusters.Add(mongoCluster); + await db.SaveChangesAsync(ct); + + return mongoCluster; + } + + /// + /// Updates connection settings for an external MongoDB cluster. If adminPassword is + /// provided, it is stored as the {name}-admin-password K8s Secret on the cluster so + /// backup and migration Jobs can authenticate exactly like managed clusters — no + /// external URI needed. The internal service endpoint ({name}-svc) is used automatically. + /// + public async Task UpdateExternalClusterSettingsAsync( + Guid tenantId, Guid mongoClusterId, + string? adminPassword, + Guid? storageLinkId, + string? backupSchedule, + int retentionDays, + int maxBackups, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .Include(c => c.StorageLink) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId && c.IsExternal, ct) + ?? throw new InvalidOperationException("External MongoDB cluster not found."); + + mongo.StorageLinkId = storageLinkId; + mongo.BackupSchedule = string.IsNullOrWhiteSpace(backupSchedule) ? null : backupSchedule.Trim(); + mongo.RetentionDays = retentionDays > 0 ? retentionDays : 30; + mongo.MaxBackups = maxBackups > 0 ? maxBackups : 20; + + await db.SaveChangesAsync(ct); + + string kubeconfig = mongo.KubernetesCluster.Kubeconfig!; + + // Store the admin password as a K8s Secret so Jobs can reference it by name, + // exactly like managed clusters. Password is never persisted in EntKube's database. + if (!string.IsNullOrWhiteSpace(adminPassword)) + { + string adminSecretManifest = BuildAdminSecretManifest(mongo.Name, mongo.Namespace, adminPassword.Trim()); + await k8sFactory.ApplyManifestAsync(adminSecretManifest, kubeconfig, ct); + } + + // Set up the scheduled backup CronJob if a schedule and storage link are configured. + // Uses the same manifest as managed clusters — the admin secret makes it identical. + if (!string.IsNullOrEmpty(mongo.BackupSchedule) && storageLinkId.HasValue) + { + StorageLink storageLink = await db.StorageLinks.FirstAsync(s => s.Id == storageLinkId.Value, ct); + mongo.StorageLink = storageLink; + + string s3SecretName = $"{mongo.Name}-s3-credentials"; + await EnsureStorageSecretsInK8sAsync(tenantId, storageLink, s3SecretName, mongo.Namespace, kubeconfig, ct); + + string cronManifest = BuildScheduledBackupCronJobManifest(mongo, storageLink, s3SecretName); + await k8sFactory.ApplyManifestAsync(cronManifest, kubeconfig, ct); + } + } + + /// + /// Registers an existing database in an external MongoDB cluster without executing + /// any MongoDB commands. Creates a tracking record so the database appears in EntKube. + /// Use for databases that already exist in the external cluster. + /// + public async Task RegisterExternalDatabaseAsync( + Guid tenantId, Guid mongoClusterId, string databaseName, string owner, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster mongo = await db.MongoClusters + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId && c.IsExternal, ct) + ?? throw new InvalidOperationException("External MongoDB cluster not found."); + + bool exists = await db.Set() + .AnyAsync(d => d.MongoClusterId == mongoClusterId + && d.Name == databaseName, ct); + + if (exists) + throw new InvalidOperationException($"Database '{databaseName}' is already tracked on this cluster."); + + MongoDatabase database = new() + { + Id = Guid.NewGuid(), + MongoClusterId = mongo.Id, + Name = databaseName, + Owner = string.IsNullOrWhiteSpace(owner) ? databaseName : owner.Trim(), + Status = MongoDatabaseStatus.Ready + }; + + db.Set().Add(database); + await db.SaveChangesAsync(ct); + + return database; + } + + /// + /// Discovers databases on an external MongoDB cluster by executing a listDatabases command + /// against the primary pod via kubectl exec. Reads the admin password from the + /// {name}-admin-password K8s Secret. Any databases not yet tracked in EntKube are added + /// automatically. System databases (admin, local, config) are skipped. + /// Fails silently if the pod is unreachable or credentials are missing. + /// + public async Task SyncExternalDatabasesAsync( + Guid tenantId, Guid mongoClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster? cluster = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .Include(c => c.Databases) + .FirstOrDefaultAsync(c => c.Id == mongoClusterId && c.TenantId == tenantId && c.IsExternal, ct); + + if (cluster is null) return; + + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + + // Read admin password from K8s Secret — same secret that backup Jobs use. + string? adminPw = await k8sFactory.GetSecretValueAsync( + $"{cluster.Name}-admin-password", "password", cluster.Namespace, kubeconfig, ct); + + // Run listDatabases on the primary pod, outputting one name per line. + // We use EJSON-free output: each line is a plain database name. + const string script = """ + db.adminCommand({listDatabases:1}).databases + .filter(d => !['admin','local','config'].includes(d.name)) + .forEach(d => print(d.name)) + """; + + string output; + try + { + // Hard timeout: if mongosh hangs (auth prompt, slow pod, etc.) we must not + // block the cluster detail page. 12 seconds is generous for a local exec. + using CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); + timeout.CancelAfter(TimeSpan.FromSeconds(12)); + + output = await k8sFactory.ExecuteMongoWithOutputAsync( + cluster.Name, cluster.Namespace, script, kubeconfig, + username: string.IsNullOrEmpty(adminPw) ? null : "admin", + password: adminPw, + ct: timeout.Token); + } + catch + { + // Pod unreachable, auth failed, timeout, mongosh not available — skip silently. + return; + } + + HashSet existingNames = cluster.Databases + .Select(d => d.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + bool changed = false; + foreach (string line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + string dbName = line.Trim(); + if (string.IsNullOrEmpty(dbName) || existingNames.Contains(dbName)) + continue; + + db.Set().Add(new MongoDatabase + { + Id = Guid.NewGuid(), + MongoClusterId = cluster.Id, + Name = dbName, + Owner = dbName, + Status = MongoDatabaseStatus.Ready + }); + existingNames.Add(dbName); + changed = true; + } + + if (changed) + await db.SaveChangesAsync(ct); + } + + /// + /// Migrates data from an external MongoDB cluster to a managed (Community Operator) cluster. + /// The Job runs on the source cluster's K8s environment so both the source and target + /// are reachable via their internal service endpoints. Both clusters must be on the same + /// Kubernetes cluster, or the source K8s cluster must have network access to the target. + /// + /// Authentication uses {source.Name}-admin-password and {target.Name}-admin-password K8s + /// Secrets — the same convention as managed clusters. Store the source admin credentials + /// via UpdateExternalClusterSettingsAsync before migrating. + /// + public async Task MigrateFromExternalToManagedAsync( + Guid tenantId, Guid sourceMongoClusterId, Guid targetMongoClusterId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster source = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == sourceMongoClusterId && c.TenantId == tenantId && c.IsExternal, ct) + ?? throw new InvalidOperationException("Source external MongoDB cluster not found."); + + // Verify the admin secret exists on the source cluster. + string? adminPw = await k8sFactory.GetSecretValueAsync( + $"{source.Name}-admin-password", "password", source.Namespace, + source.KubernetesCluster.Kubeconfig!, ct); + + if (string.IsNullOrEmpty(adminPw)) + throw new InvalidOperationException( + $"Admin credentials not found. Create the '{source.Name}-admin-password' K8s Secret " + + "by saving an admin password via the cluster settings."); + + MongoCluster target = await db.MongoClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == targetMongoClusterId && c.TenantId == tenantId && !c.IsExternal, ct) + ?? throw new InvalidOperationException("Target managed MongoDB cluster not found."); + + string jobName = $"{source.Name}-to-{target.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + + // The Job runs on the source cluster's K8s environment. + // Both clusters must share network access for cross-cluster migrations. + string manifest = BuildMigrateFromExternalManifest(jobName, source, target); + await k8sFactory.ApplyManifestAsync(manifest, source.KubernetesCluster.Kubeconfig!, ct); + } + /// /// Gets the live detail for a Percona MongoDB cluster including pod status. /// Queries the PerconaServerMongoDB CRD status and lists pods. @@ -614,6 +1467,10 @@ public class MongoService( ParseClusterStatus(clusterJson, detail); + // Persist discovered version and databases back to the DB so the cluster + // header shows the correct version and db count without re-querying K8s. + await PersistDiscoveredMetadataAsync(cluster, detail, ct); + // Query all pods in the namespace and filter by StatefulSet naming convention // ({name}-N). Label-selector-based queries are unreliable across Community // Operator versions (some use `app`, some use `app.kubernetes.io/name`). @@ -633,6 +1490,28 @@ public class MongoService( detail.Phase = "Unable to reach cluster"; } + // For external clusters: supplement CRD-based discovery with a live listDatabases + // call via kubectl exec. Runs after the CRD query so it catches databases that + // exist in MongoDB but are not declared in spec.users. Must never throw — any + // failure here would kill the detail view that already loaded successfully above. + if (cluster.IsExternal) + { + try + { + await SyncExternalDatabasesAsync(cluster.TenantId, cluster.Id, ct); + + // Reload databases so the detail reflects any newly synced ones. + using ApplicationDbContext refreshDb = dbFactory.CreateDbContext(); + cluster.Databases = await refreshDb.Set() + .Where(d => d.MongoClusterId == cluster.Id) + .ToListAsync(ct); + } + catch + { + // Sync failures are non-fatal — cluster status was already loaded above. + } + } + // Fetch backup job status independently — has its own try/catch so a cluster-query // failure above does not prevent backup status from being updated. // Scan K8s Jobs for completed backup jobs so scheduled backups appear in the UI @@ -697,6 +1576,7 @@ public class MongoService( if (lower == "running" && readyMembers >= expectedMembers) { if (currentStatus is MongoClusterStatus.Creating + or MongoClusterStatus.Upgrading or MongoClusterStatus.Restoring or MongoClusterStatus.Failed) { @@ -1113,6 +1993,23 @@ public class MongoService( // ──────── Private Helpers ──────── + /// + /// Returns the Docker Hub image tag for a given MongoDB version string. + /// Uses major.minor only (e.g. "8.0") because Docker Hub carries minor-stream + /// tags reliably, but not every X.Y.Z patch tag that the Percona operator accepts. + /// Falls back to "mongo:8.0" for unknown/empty versions. + /// + private static string MongoImage(string version) + { + if (string.IsNullOrEmpty(version) || version == "unknown") + return "mongo:8.0"; + + string[] parts = version.Split('.'); + return parts.Length >= 2 + ? $"mongo:{parts[0]}.{parts[1]}" + : $"mongo:{version}"; + } + /// /// Ensures the S3 storage credentials are available as a Kubernetes Secret /// in the target namespace so backup Jobs can access the bucket. @@ -1287,7 +2184,7 @@ public class MongoService( /// pre-existing {name}-admin-password Secret so the operator can configure SCRAM /// authentication and create the StatefulSet. /// - private static string BuildClusterManifest(MongoCluster mongo, StorageLink? storageLink, string? s3SecretName) + private static string BuildClusterManifest(MongoCluster mongo) { // Community Operator requires a full X.Y.Z version; coerce X.Y → X.Y.0. string version = mongo.MongoVersion.Count(c => c == '.') < 2 @@ -1325,6 +2222,44 @@ public class MongoService( sb.AppendLine(" db: admin"); sb.AppendLine(" statefulSet:"); sb.AppendLine(" spec:"); + + bool hasCpuOrMemory = !string.IsNullOrWhiteSpace(mongo.CpuRequest) + || !string.IsNullOrWhiteSpace(mongo.CpuLimit) + || !string.IsNullOrWhiteSpace(mongo.MemoryRequest) + || !string.IsNullOrWhiteSpace(mongo.MemoryLimit); + + if (hasCpuOrMemory) + { + sb.AppendLine(" template:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" containers:"); + sb.AppendLine(" - name: mongod"); + sb.AppendLine(" resources:"); + + bool hasRequests = !string.IsNullOrWhiteSpace(mongo.CpuRequest) + || !string.IsNullOrWhiteSpace(mongo.MemoryRequest); + bool hasLimits = !string.IsNullOrWhiteSpace(mongo.CpuLimit) + || !string.IsNullOrWhiteSpace(mongo.MemoryLimit); + + if (hasRequests) + { + sb.AppendLine(" requests:"); + if (!string.IsNullOrWhiteSpace(mongo.CpuRequest)) + sb.AppendLine($" cpu: \"{mongo.CpuRequest}\""); + if (!string.IsNullOrWhiteSpace(mongo.MemoryRequest)) + sb.AppendLine($" memory: \"{mongo.MemoryRequest}\""); + } + + if (hasLimits) + { + sb.AppendLine(" limits:"); + if (!string.IsNullOrWhiteSpace(mongo.CpuLimit)) + sb.AppendLine($" cpu: \"{mongo.CpuLimit}\""); + if (!string.IsNullOrWhiteSpace(mongo.MemoryLimit)) + sb.AppendLine($" memory: \"{mongo.MemoryLimit}\""); + } + } + sb.AppendLine(" volumeClaimTemplates:"); sb.AppendLine(" - metadata:"); sb.AppendLine(" name: data-volume"); @@ -1347,7 +2282,9 @@ public class MongoService( string backupName, MongoCluster mongo, StorageLink storageLink, string s3SecretName) { string host = $"{mongo.Name}-svc.{mongo.Namespace}.svc.cluster.local"; - string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}"; + // readPreference=secondaryPreferred: mongodump reads from a secondary when available, + // so a rolling primary restart (e.g. during an upgrade) doesn't kill the backup job. + string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}&readPreference=secondaryPreferred&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; string s3Path = $"s3://{storageLink.BucketName}/{mongo.Name}/{backupName}.archive"; StringBuilder sb = new(); @@ -1371,9 +2308,9 @@ public class MongoService( sb.AppendLine(" emptyDir: {}"); sb.AppendLine(" initContainers:"); sb.AppendLine(" - name: dump"); - sb.AppendLine($" image: mongo:{mongo.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(mongo.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); - sb.AppendLine($" args: [\"mongodump --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive\"]"); + sb.AppendLine($" args: [\"mongodump --uri=\\\"{mongoUri}\\\" --numParallelCollections=1 --gzip --archive=/backup/dump.archive\"]"); sb.AppendLine(" env:"); sb.AppendLine(" - name: MONGO_ADMIN_PASSWORD"); sb.AppendLine(" valueFrom:"); @@ -1418,7 +2355,7 @@ public class MongoService( string sourceBackupName, string s3SecretName) { string host = $"{mongo.Name}-svc.{mongo.Namespace}.svc.cluster.local"; - string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}"; + string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=10000"; string mongoshUri = $"mongodb://{host}:27017/?replicaSet={mongo.Name}"; string s3Path = $"s3://{storageLink.BucketName}/{mongo.Name}/{sourceBackupName}.archive"; @@ -1463,7 +2400,7 @@ public class MongoService( sb.AppendLine(" - name: backup-data"); sb.AppendLine(" mountPath: /backup"); sb.AppendLine(" - name: wait-for-mongo"); - sb.AppendLine($" image: mongo:{mongo.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(mongo.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); sb.AppendLine($" args: [\"until mongosh \\\"{mongoshUri}\\\" --username admin --password \\\"${{MONGO_ADMIN_PASSWORD}}\\\" --authenticationDatabase admin --eval 'db.adminCommand({{ping:1}})' --quiet; do echo 'waiting for MongoDB...'; sleep 5; done\"]"); sb.AppendLine(" env:"); @@ -1474,9 +2411,9 @@ public class MongoService( sb.AppendLine(" key: password"); sb.AppendLine(" containers:"); sb.AppendLine(" - name: restore"); - sb.AppendLine($" image: mongo:{mongo.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(mongo.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); - sb.AppendLine($" args: [\"mongorestore --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive --drop\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive --drop --numInsertionWorkersPerCollection=1 --batchSize=100\"]"); sb.AppendLine(" env:"); sb.AppendLine(" - name: MONGO_ADMIN_PASSWORD"); sb.AppendLine(" valueFrom:"); @@ -1500,7 +2437,7 @@ public class MongoService( StorageLink storageLink, string sourceBackupName, string s3SecretName) { string host = $"{targetCluster.Name}-svc.{targetCluster.Namespace}.svc.cluster.local"; - string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={targetCluster.Name}"; + string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={targetCluster.Name}&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=10000"; string mongoshUri = $"mongodb://{host}:27017/?replicaSet={targetCluster.Name}"; string s3Path = $"s3://{storageLink.BucketName}/{sourceClusterName}/{sourceBackupName}.archive"; @@ -1545,7 +2482,7 @@ public class MongoService( sb.AppendLine(" - name: backup-data"); sb.AppendLine(" mountPath: /backup"); sb.AppendLine(" - name: wait-for-mongo"); - sb.AppendLine($" image: mongo:{targetCluster.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(targetCluster.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); sb.AppendLine($" args: [\"until mongosh \\\"{mongoshUri}\\\" --username admin --password \\\"${{MONGO_ADMIN_PASSWORD}}\\\" --authenticationDatabase admin --eval 'db.adminCommand({{ping:1}})' --quiet; do echo 'waiting for MongoDB...'; sleep 5; done\"]"); sb.AppendLine(" env:"); @@ -1556,9 +2493,9 @@ public class MongoService( sb.AppendLine(" key: password"); sb.AppendLine(" containers:"); sb.AppendLine(" - name: restore"); - sb.AppendLine($" image: mongo:{targetCluster.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(targetCluster.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); - sb.AppendLine($" args: [\"mongorestore --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive --drop\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive --drop --numInsertionWorkersPerCollection=1 --batchSize=100\"]"); sb.AppendLine(" env:"); sb.AppendLine(" - name: MONGO_ADMIN_PASSWORD"); sb.AppendLine(" valueFrom:"); @@ -1626,9 +2563,9 @@ public class MongoService( sb.AppendLine(" mountPath: /backup"); sb.AppendLine(" containers:"); sb.AppendLine(" - name: restore"); - sb.AppendLine($" image: mongo:{sourceCluster.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(sourceCluster.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); - sb.AppendLine($" args: [\"mongorestore --uri='{externalMongoUri}' --gzip --archive=/backup/dump.archive --drop\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{externalMongoUri}\\\" --gzip --archive=/backup/dump.archive --drop --numInsertionWorkersPerCollection=1 --batchSize=100\"]"); sb.AppendLine(" volumeMounts:"); sb.AppendLine(" - name: backup-data"); sb.AppendLine(" mountPath: /backup"); @@ -1646,7 +2583,7 @@ public class MongoService( MongoCluster mongo, StorageLink storageLink, string s3SecretName) { string host = $"{mongo.Name}-svc.{mongo.Namespace}.svc.cluster.local"; - string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}"; + string mongoUri = $"mongodb://admin:${{MONGO_ADMIN_PASSWORD}}@{host}:27017/?authSource=admin&replicaSet={mongo.Name}&readPreference=secondaryPreferred&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; string s3Base = $"s3://{storageLink.BucketName}/{mongo.Name}"; StringBuilder sb = new(); @@ -1682,9 +2619,9 @@ public class MongoService( sb.AppendLine(" emptyDir: {}"); sb.AppendLine(" initContainers:"); sb.AppendLine(" - name: dump"); - sb.AppendLine($" image: mongo:{mongo.MongoVersion}"); + sb.AppendLine($" image: {MongoImage(mongo.MongoVersion)}"); sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); - sb.AppendLine($" args: [\"mongodump --uri=\\\"{mongoUri}\\\" --gzip --archive=/backup/dump.archive\"]"); + sb.AppendLine($" args: [\"mongodump --uri=\\\"{mongoUri}\\\" --numParallelCollections=1 --gzip --archive=/backup/dump.archive\"]"); sb.AppendLine(" env:"); sb.AppendLine(" - name: MONGO_ADMIN_PASSWORD"); sb.AppendLine(" valueFrom:"); @@ -1719,12 +2656,156 @@ public class MongoService( return sb.ToString(); } + /// + /// Builds a migration Job that uses the same credential convention as managed clusters: + /// source dump authenticates via {source.Name}-admin-password, restore authenticates + /// via {target.Name}-admin-password. Both secrets must exist on the source K8s cluster + /// (the Job runs there). The target host is reached via its service FQDN. + /// + private static string BuildMigrateFromExternalManifest( + string jobName, MongoCluster source, MongoCluster target) + { + string sourceImage = MongoImage(source.MongoVersion); + string targetImage = MongoImage(target.MongoVersion); + + string sourceHost = $"{source.Name}-svc.{source.Namespace}.svc.cluster.local"; + string sourceUri = $"mongodb://admin:${{SOURCE_ADMIN_PASSWORD}}@{sourceHost}:27017/?authSource=admin&replicaSet={source.Name}&readPreference=secondaryPreferred&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; + + string targetHost = $"{target.Name}-svc.{target.Namespace}.svc.cluster.local"; + string targetUri = $"mongodb://admin:${{TARGET_ADMIN_PASSWORD}}@{targetHost}:27017/?authSource=admin&replicaSet={target.Name}&socketTimeoutMS=0&connectTimeoutMS=30000&heartbeatFrequencyMS=5000&serverSelectionTimeoutMS=60000"; + + StringBuilder sb = new(); + sb.AppendLine("apiVersion: batch/v1"); + sb.AppendLine("kind: Job"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {jobName}"); + sb.AppendLine($" namespace: {source.Namespace}"); + sb.AppendLine(" labels:"); + sb.AppendLine($" entkube.io/mongo-cluster: {source.Name}"); + sb.AppendLine($" entkube.io/migration-target: {target.Name}"); + string sourceWaitUri2 = $"mongodb://admin:${{SOURCE_ADMIN_PASSWORD}}@{sourceHost}:27017/?authSource=admin&replicaSet={source.Name}&connectTimeoutMS=10000&serverSelectionTimeoutMS=15000"; + + sb.AppendLine("spec:"); + sb.AppendLine(" backoffLimit: 2"); + sb.AppendLine(" ttlSecondsAfterFinished: 86400"); + sb.AppendLine(" template:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" restartPolicy: Never"); + sb.AppendLine(" volumes:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" emptyDir: {}"); + sb.AppendLine(" initContainers:"); + sb.AppendLine(" - name: wait-for-source"); + sb.AppendLine($" image: {sourceImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"until mongosh \\\"{sourceWaitUri2}\\\" --username admin --password \\\"${{SOURCE_ADMIN_PASSWORD}}\\\" --authenticationDatabase admin --eval 'db.adminCommand({{ping:1}})' --quiet; do echo 'waiting for source...'; sleep 10; done\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: SOURCE_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {source.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" - name: dump-from-source"); + sb.AppendLine($" image: {sourceImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongodump --uri=\\\"{sourceUri}\\\" --numParallelCollections=1 --gzip --archive=/backup/dump.archive\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: SOURCE_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {source.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" mountPath: /backup"); + sb.AppendLine(" containers:"); + sb.AppendLine(" - name: restore-to-managed"); + sb.AppendLine($" image: {targetImage}"); + sb.AppendLine(" command: [\"/bin/sh\", \"-c\"]"); + sb.AppendLine($" args: [\"mongorestore --uri=\\\"{targetUri}\\\" --gzip --archive=/backup/dump.archive --drop --numInsertionWorkersPerCollection=1 --batchSize=100\"]"); + sb.AppendLine(" env:"); + sb.AppendLine(" - name: TARGET_ADMIN_PASSWORD"); + sb.AppendLine(" valueFrom:"); + sb.AppendLine(" secretKeyRef:"); + sb.AppendLine($" name: {target.Name}-admin-password"); + sb.AppendLine(" key: password"); + sb.AppendLine(" volumeMounts:"); + sb.AppendLine(" - name: backup-data"); + sb.AppendLine(" mountPath: /backup"); + return sb.ToString(); + } + // ──────── JSON Parsing ──────── /// /// Parses the MongoDBCommunity CRD JSON to extract phase and ready member count. /// Community Operator status fields: status.phase, status.currentMongoDBMembers. /// + /// + /// Persists version and databases discovered from the live MongoDBCommunity CRD into + /// the EntKube database, so the cluster header shows them without a live K8s query. + /// Only updates fields that are still at their "unknown" placeholder values, so manual + /// edits are not overwritten on subsequent loads. + /// + private async Task PersistDiscoveredMetadataAsync( + MongoCluster cluster, MongoClusterDetail detail, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MongoCluster? record = await db.MongoClusters + .Include(c => c.Databases) + .FirstOrDefaultAsync(c => c.Id == cluster.Id, ct); + + if (record is null) + { + return; + } + + bool changed = false; + + if (!string.IsNullOrEmpty(detail.Version) + && (record.MongoVersion == "unknown" || record.MongoVersion != detail.Version)) + { + record.MongoVersion = detail.Version; + // Keep the in-memory cluster in sync so the UI reflects it immediately. + cluster.MongoVersion = detail.Version; + changed = true; + } + + // Create MongoDatabase records for any databases discovered in spec.users that + // are not already tracked. Existing databases are left untouched. + HashSet existingNames = record.Databases + .Select(d => d.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + foreach ((string dbName, string owner) in detail.DiscoveredDatabases) + { + if (existingNames.Contains(dbName)) + { + continue; + } + + MongoDatabase newDb = new() + { + Id = Guid.NewGuid(), + MongoClusterId = record.Id, + Name = dbName, + Owner = string.IsNullOrEmpty(owner) ? dbName : owner, + Status = MongoDatabaseStatus.Ready + }; + + db.Set().Add(newDb); + cluster.Databases.Add(newDb); + existingNames.Add(dbName); + changed = true; + } + + if (changed) + { + await db.SaveChangesAsync(ct); + } + } + private static void ParseClusterStatus(string json, MongoClusterDetail detail) { using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(json); @@ -1767,6 +2848,53 @@ public class MongoService( } } + // Read spec.version (e.g. "6.0.5") and spec.users to discover databases. + if (root.TryGetProperty("spec", out System.Text.Json.JsonElement spec)) + { + if (spec.TryGetProperty("version", out System.Text.Json.JsonElement versionEl)) + { + detail.Version = versionEl.GetString(); + } + + // spec.users[].roles[].db reveals which databases each user has access to. + // Skip system databases that are always present. + HashSet systemDbs = ["admin", "local", "config"]; + + if (spec.TryGetProperty("users", out System.Text.Json.JsonElement users) + && users.ValueKind == System.Text.Json.JsonValueKind.Array) + { + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (System.Text.Json.JsonElement user in users.EnumerateArray()) + { + string? userName = user.TryGetProperty("name", out System.Text.Json.JsonElement nameEl2) + ? nameEl2.GetString() : null; + + if (!user.TryGetProperty("roles", out System.Text.Json.JsonElement roles) + || roles.ValueKind != System.Text.Json.JsonValueKind.Array) + { + continue; + } + + foreach (System.Text.Json.JsonElement role in roles.EnumerateArray()) + { + if (!role.TryGetProperty("db", out System.Text.Json.JsonElement dbEl)) + { + continue; + } + + string? dbName = dbEl.GetString(); + + if (string.IsNullOrEmpty(dbName) || systemDbs.Contains(dbName) || !seen.Add(dbName)) + { + continue; + } + + detail.DiscoveredDatabases.Add((dbName, userName ?? "")); + } + } + } + } + // Derive the endpoint from the resource metadata name, which is stored on the cluster record. if (root.TryGetProperty("metadata", out System.Text.Json.JsonElement metadata) && metadata.TryGetProperty("name", out System.Text.Json.JsonElement nameEl) @@ -1871,6 +2999,9 @@ public class MongoClusterDetail public string? Message { get; set; } public int ReadyMembers { get; set; } public string? Endpoint { get; set; } + public string? Version { get; set; } + /// Databases discovered from spec.users roles in the MongoDBCommunity CRD. + public List<(string DbName, string Owner)> DiscoveredDatabases { get; set; } = []; public List Pods { get; set; } = []; public List Backups { get; set; } = []; } diff --git a/src/EntKube.Web/Services/NotificationService.cs b/src/EntKube.Web/Services/NotificationService.cs new file mode 100644 index 0000000..f2006fd --- /dev/null +++ b/src/EntKube.Web/Services/NotificationService.cs @@ -0,0 +1,248 @@ +using System.Net; +using System.Net.Mail; +using System.Text; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using EntKube.Web.Data; + +namespace EntKube.Web.Services; + +public class NotificationService( + IHttpClientFactory httpClientFactory, + IDbContextFactory dbFactory, + IConfiguration configuration, + ILogger logger) +{ + public async Task SendAsync( + AlertIncident incident, + NotificationChannel channel, + bool isFiring, + CancellationToken ct = default) + { + if (!channel.IsEnabled) return; + if (!MeetsSeverityFilter(incident.Severity, channel.SeverityFilter)) return; + + bool success = false; + string? error = null; + + try + { + success = channel.Type switch + { + NotificationChannelType.Slack => await SendSlackAsync(incident, channel, isFiring, ct), + NotificationChannelType.Teams => await SendTeamsAsync(incident, channel, isFiring, ct), + NotificationChannelType.Email => await SendEmailAsync(incident, channel, isFiring), + NotificationChannelType.Webhook => await SendWebhookAsync(incident, channel, isFiring, ct), + _ => false + }; + } + catch (Exception ex) + { + error = ex.Message; + logger.LogWarning(ex, "Notification failed for channel {Channel} ({Type})", channel.Name, channel.Type); + } + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + db.NotificationDeliveries.Add(new NotificationDelivery + { + IncidentId = incident.Id, + ChannelId = channel.Id, + IsFiring = isFiring, + Success = success, + Error = error?.Length > 1000 ? error[..1000] : error + }); + await db.SaveChangesAsync(ct); + } + + public async Task SendTestAsync(NotificationChannel channel, CancellationToken ct = default) + { + AlertIncident fake = new() + { + Id = Guid.Empty, + ClusterId = Guid.Empty, + Fingerprint = "test", + AlertName = "TestAlert", + Severity = "info", + Summary = "This is a test notification from EntKube.", + StartsAt = DateTime.UtcNow, + Status = IncidentStatus.Active, + Cluster = new KubernetesCluster { Name = "test-cluster", ApiServerUrl = "" } + }; + + bool success = false; + string? error = null; + try + { + success = channel.Type switch + { + NotificationChannelType.Slack => await SendSlackAsync(fake, channel, true, ct), + NotificationChannelType.Teams => await SendTeamsAsync(fake, channel, true, ct), + NotificationChannelType.Email => await SendEmailAsync(fake, channel, true), + NotificationChannelType.Webhook => await SendWebhookAsync(fake, channel, true, ct), + _ => false + }; + } + catch (Exception ex) + { + error = ex.Message; + } + + if (!success) + throw new InvalidOperationException(error ?? "Test notification failed"); + } + + private async Task SendSlackAsync( + AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct) + { + using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson); + string webhookUrl = config.RootElement.GetProperty("webhookUrl").GetString() + ?? throw new InvalidOperationException("Slack webhookUrl missing"); + + string emoji = incident.Severity switch + { + "critical" => ":red_circle:", + "warning" => ":warning:", + _ => ":information_source:" + }; + string state = isFiring ? "FIRING" : "RESOLVED"; + string text = $"{emoji} *[{state}] {incident.AlertName}*\n" + + $"Severity: {incident.Severity} | Cluster: {incident.Cluster?.Name ?? incident.ClusterId.ToString()}\n" + + $"Started: {incident.StartsAt:u}\n" + + $"{(string.IsNullOrEmpty(incident.Summary) ? "" : incident.Summary)}"; + + string body = JsonSerializer.Serialize(new { text }); + using HttpClient http = httpClientFactory.CreateClient("Notifications"); + using StringContent content = new(body, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await http.PostAsync(webhookUrl, content, ct); + return response.IsSuccessStatusCode; + } + + private async Task SendTeamsAsync( + AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct) + { + using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson); + string webhookUrl = config.RootElement.GetProperty("webhookUrl").GetString() + ?? throw new InvalidOperationException("Teams webhookUrl missing"); + + string color = incident.Severity switch + { + "critical" => "attention", + "warning" => "warning", + _ => "accent" + }; + string state = isFiring ? "FIRING" : "RESOLVED"; + + object card = new + { + type = "message", + attachments = new[] + { + new + { + contentType = "application/vnd.microsoft.card.adaptive", + content = new + { + type = "AdaptiveCard", + version = "1.4", + body = new object[] + { + new { type = "TextBlock", text = $"[{state}] {incident.AlertName}", weight = "Bolder", size = "Medium", color }, + new { type = "FactSet", facts = new[] + { + new { title = "Severity", value = incident.Severity }, + new { title = "Cluster", value = incident.Cluster?.Name ?? incident.ClusterId.ToString() }, + new { title = "Started", value = incident.StartsAt.ToString("u") }, + new { title = "Summary", value = string.IsNullOrEmpty(incident.Summary) ? "-" : incident.Summary } + }} + } + } + } + } + }; + + string body = JsonSerializer.Serialize(card); + using HttpClient http = httpClientFactory.CreateClient("Notifications"); + using StringContent content = new(body, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await http.PostAsync(webhookUrl, content, ct); + return response.IsSuccessStatusCode; + } + + private Task SendEmailAsync(AlertIncident incident, NotificationChannel channel, bool isFiring) + { + using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson); + string to = config.RootElement.GetProperty("to").GetString() + ?? throw new InvalidOperationException("Email 'to' missing"); + + string? smtpHost = configuration["Smtp:Host"]; + if (string.IsNullOrEmpty(smtpHost)) + { + logger.LogWarning("SMTP not configured — skipping email notification for channel {Channel}", channel.Name); + return Task.FromResult(false); + } + + int smtpPort = configuration.GetValue("Smtp:Port", 587); + string from = configuration["Smtp:FromAddress"] ?? "alerts@entkube.io"; + string? smtpUser = configuration["Smtp:Username"]; + string? smtpPass = configuration["Smtp:Password"]; + + string state = isFiring ? "FIRING" : "RESOLVED"; + string subject = $"[EntKube] [{state}] {incident.AlertName} ({incident.Severity})"; + string body = $"Alert: {incident.AlertName}\n" + + $"Status: {state}\n" + + $"Severity: {incident.Severity}\n" + + $"Cluster: {incident.Cluster?.Name ?? incident.ClusterId.ToString()}\n" + + $"Started: {incident.StartsAt:u}\n" + + $"Summary: {incident.Summary}\n" + + $"Description: {incident.Description}"; + + using SmtpClient smtp = new(smtpHost, smtpPort); + smtp.EnableSsl = true; + if (!string.IsNullOrEmpty(smtpUser)) + smtp.Credentials = new NetworkCredential(smtpUser, smtpPass); + + using MailMessage mail = new(from, to, subject, body); + smtp.Send(mail); + return Task.FromResult(true); + } + + private async Task SendWebhookAsync( + AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct) + { + using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson); + string url = config.RootElement.GetProperty("url").GetString() + ?? throw new InvalidOperationException("Webhook url missing"); + + string? token = config.RootElement.TryGetProperty("token", out JsonElement t) + ? t.GetString() + : null; + + object payload = new + { + firing = isFiring, + alertName = incident.AlertName, + severity = incident.Severity, + summary = incident.Summary, + description = incident.Description, + clusterName = incident.Cluster?.Name ?? incident.ClusterId.ToString(), + startsAt = incident.StartsAt, + endsAt = incident.EndsAt, + status = incident.Status.ToString() + }; + + using HttpClient http = httpClientFactory.CreateClient("Notifications"); + if (!string.IsNullOrEmpty(token)) + http.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + + using StringContent content = new(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); + HttpResponseMessage response = await http.PostAsync(url, content, ct); + return response.IsSuccessStatusCode; + } + + private static bool MeetsSeverityFilter(string severity, AlertSeverityFilter filter) => filter switch + { + AlertSeverityFilter.CriticalOnly => severity == "critical", + AlertSeverityFilter.WarningAndAbove => severity is "critical" or "warning", + _ => true + }; +} diff --git a/src/EntKube.Web/Services/OpenStackS3Service.cs b/src/EntKube.Web/Services/OpenStackS3Service.cs index aa38337..b4180c1 100644 --- a/src/EntKube.Web/Services/OpenStackS3Service.cs +++ b/src/EntKube.Web/Services/OpenStackS3Service.cs @@ -629,6 +629,87 @@ public class OpenStackS3Service(VaultService vaultService, IHttpClientFactory ht await s3Client.PutBucketPolicyAsync(putRequest, ct); } + // ──────── Pre-built Client Overloads (K8s proxy) ──────── + // Called by StorageService when a MinIO link needs to be reached via the + // Kubernetes API server proxy. The caller owns the AmazonS3Client lifecycle. + + public async Task?> GetBucketCorsAsync( + AmazonS3Client s3Client, string bucketName, CancellationToken ct = default) + { + try + { + GetCORSConfigurationRequest request = new() { BucketName = bucketName }; + GetCORSConfigurationResponse response = await s3Client.GetCORSConfigurationAsync(request, ct); + return response.Configuration.Rules + .Select(r => new S3CorsRule + { + AllowedOrigins = r.AllowedOrigins, + AllowedMethods = r.AllowedMethods, + AllowedHeaders = r.AllowedHeaders, + MaxAgeSeconds = r.MaxAgeSeconds ?? 0 + }) + .ToList(); + } + catch (AmazonS3Exception ex) when (ex.ErrorCode == "NoSuchCORSConfiguration") + { + return null; + } + } + + public async Task SetBucketCorsAsync( + AmazonS3Client s3Client, string bucketName, List rules, CancellationToken ct = default) + { + if (rules.Count == 0) + { + await s3Client.DeleteCORSConfigurationAsync( + new DeleteCORSConfigurationRequest { BucketName = bucketName }, ct); + return; + } + + CORSConfiguration corsConfig = new() + { + Rules = rules.Select(r => new CORSRule + { + AllowedOrigins = r.AllowedOrigins, + AllowedMethods = r.AllowedMethods, + AllowedHeaders = r.AllowedHeaders, + MaxAgeSeconds = r.MaxAgeSeconds + }).ToList() + }; + + await s3Client.PutCORSConfigurationAsync( + new PutCORSConfigurationRequest { BucketName = bucketName, Configuration = corsConfig }, ct); + } + + public async Task GetBucketPolicyAsync( + AmazonS3Client s3Client, string bucketName, CancellationToken ct = default) + { + try + { + GetBucketPolicyResponse response = await s3Client.GetBucketPolicyAsync( + new GetBucketPolicyRequest { BucketName = bucketName }, ct); + return response.Policy; + } + catch (AmazonS3Exception ex) when (ex.ErrorCode == "NoSuchBucketPolicy") + { + return null; + } + } + + public async Task SetBucketPolicyAsync( + AmazonS3Client s3Client, string bucketName, string? policyJson, CancellationToken ct = default) + { + if (policyJson is null) + { + await s3Client.DeleteBucketPolicyAsync( + new DeleteBucketPolicyRequest { BucketName = bucketName }, ct); + return; + } + + await s3Client.PutBucketPolicyAsync( + new() { BucketName = bucketName, Policy = policyJson }, ct); + } + // ──────── EC2 Credential Rotation ──────── /// diff --git a/src/EntKube.Web/Services/PrometheusService.cs b/src/EntKube.Web/Services/PrometheusService.cs index bda3ebb..09f866a 100644 --- a/src/EntKube.Web/Services/PrometheusService.cs +++ b/src/EntKube.Web/Services/PrometheusService.cs @@ -1,8 +1,10 @@ using System.Globalization; using System.Text; +using System.Text.RegularExpressions; using System.Text.Json; using EntKube.Web.Data; using k8s; +using k8s.Models; using Microsoft.EntityFrameworkCore; namespace EntKube.Web.Services; @@ -118,6 +120,95 @@ public class SilenceMatcher public bool IsEqual { get; set; } = true; } +/// +/// Namespace-scoped resource metrics for a single app deployment. +/// +public class DeploymentMetricsSummary +{ + public string Namespace { get; set; } = ""; + public double CpuCores { get; set; } + public double MemoryMiB { get; set; } + public int PodCount { get; set; } + public int RestartCount { get; set; } + public DateTime QueriedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// CPU%, memory%, and pod-count time series for a cluster's monitoring graphs. +/// +public sealed record ClusterMetricsHistory( + List CpuPercent, + List MemoryPercent, + List PodCount); + +/// +/// A single Prometheus scrape target with its health status. +/// +public class ScrapeTarget +{ + public string Pool { get; set; } = ""; + public string ScrapeUrl { get; set; } = ""; + public string Health { get; set; } = ""; // "up" | "down" | "unknown" + public string LastError { get; set; } = ""; + public DateTime LastScrape { get; set; } + public double LastScrapeDurationSeconds { get; set; } + public Dictionary Labels { get; set; } = new(); +} + +/// +/// A single Prometheus alerting rule with its current evaluation state. +/// +public class AlertRule +{ + public string Name { get; set; } = ""; + public string GroupName { get; set; } = ""; + public string Query { get; set; } = ""; + public string State { get; set; } = ""; // inactive, pending, firing + public string Severity { get; set; } = ""; + public string Summary { get; set; } = ""; + public string RunbookUrl { get; set; } = ""; + public double DurationSeconds { get; set; } + public double EvaluationTimeSeconds { get; set; } + public Dictionary Labels { get; set; } = new(); + public Dictionary Annotations { get; set; } = new(); +} + +/// +/// RabbitMQ cluster metrics scraped from Prometheus via the RabbitMQ Prometheus plugin. +/// +public class RabbitMQMetricsSummary +{ + public string ClusterName { get; set; } = ""; + public int Nodes { get; set; } + public long TotalMessages { get; set; } + public long ReadyMessages { get; set; } + public long UnackedMessages { get; set; } + public int Connections { get; set; } + public int Channels { get; set; } + public double PublishRatePerSec { get; set; } + public double DeliverRatePerSec { get; set; } + public DateTime QueriedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// Metrics for a CloudNativePG cluster scraped from Prometheus. +/// +public class CnpgMetricsSummary +{ + public string ClusterName { get; set; } = ""; + public double ReplicationLagSeconds { get; set; } + public int TotalBackends { get; set; } + public int ActiveQueries { get; set; } + public List DatabaseSizes { get; set; } = []; + public DateTime QueriedAt { get; set; } = DateTime.UtcNow; +} + +public class CnpgDatabaseSize +{ + public string DatabaseName { get; set; } = ""; + public double SizeMiB { get; set; } +} + /// /// Connects to a kube-prometheus-stack running in a cluster and retrieves /// health metrics, time-series data, alerts, and silences via port-forwarded @@ -127,7 +218,9 @@ public class SilenceMatcher /// Kubernetes client from the stored kubeconfig, and queries the Prometheus /// HTTP API via the K8s API server proxy endpoint. /// -public class PrometheusService(IDbContextFactory dbFactory) +public class PrometheusService( + IDbContextFactory dbFactory, + ILogger logger) { // ──────── Public API ──────── @@ -139,74 +232,62 @@ public class PrometheusService(IDbContextFactory dbFactory public async Task> GetClusterHealthAsync( Guid clusterId, CancellationToken ct = default) { - using ApplicationDbContext db = dbFactory.CreateDbContext(); + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult.Failure(error!); - // Load the cluster with its components to find the prometheus instance. - - KubernetesCluster? cluster = await db.KubernetesClusters - .Include(c => c.Components) - .FirstOrDefaultAsync(c => c.Id == clusterId, ct); - - if (cluster is null) - { - return KubernetesOperationResult.Failure("Cluster not found."); - } - - if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) - { - return KubernetesOperationResult.Failure( - "No kubeconfig configured for this cluster."); - } - - // Find the prometheus component on this cluster. - - ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => - c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); - - if (prometheusComponent is null) - { - return KubernetesOperationResult.Failure( - "No prometheus component found on this cluster."); - } - - PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); - - try - { - Kubernetes client = CreateClient(cluster.Kubeconfig); - string baseUrl = $"/api/v1/namespaces/{config.Namespace}/services/{config.ServiceName}:{config.ServicePort}/proxy"; - - // Query key health metrics in parallel. - - Task cpuTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=100 - (avg(rate(node_cpu_seconds_total{{mode=\"idle\"}}[5m])) * 100)", ct); - Task memTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=100 - (avg(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100)", ct); - Task nodesTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=count(kube_node_info)", ct); - Task readyNodesTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=count(kube_node_status_condition{{condition=\"Ready\",status=\"true\"}})", ct); - Task podsTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=count(kube_pod_info)", ct); - Task runningPodsTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=sum(kube_pod_status_phase{{phase=\"Running\"}})", ct); - Task diskTask = ProxyGetAsync(client, $"{baseUrl}/api/v1/query?query=100 - (avg(node_filesystem_avail_bytes{{mountpoint=\"/\"}} / node_filesystem_size_bytes{{mountpoint=\"/\"}}) * 100)", ct); - - await Task.WhenAll(cpuTask, memTask, nodesTask, readyNodesTask, podsTask, runningPodsTask, diskTask); - - ClusterHealthSummary health = new() + return await WithServiceAsync( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => { - CpuUsagePercent = ExtractScalarValue(await cpuTask), - MemoryUsagePercent = ExtractScalarValue(await memTask), - TotalNodes = (int)ExtractScalarValue(await nodesTask), - ReadyNodes = (int)ExtractScalarValue(await readyNodesTask), - TotalPods = (int)ExtractScalarValue(await podsTask), - RunningPods = (int)ExtractScalarValue(await runningPodsTask), - DiskUsagePercent = ExtractScalarValue(await diskTask), - QueriedAt = DateTime.UtcNow - }; + static async Task Scalar(HttpClient h, string url, CancellationToken t) => + ExtractScalarValue(await h.GetStringAsync(url, t)); - return KubernetesOperationResult.Success(health); - } - catch (Exception ex) - { - return KubernetesOperationResult.Failure( - $"Failed to query Prometheus: {ex.Message}"); - } + // Detect which metric sources are available so we can use the right queries. + double hasKsm = await Scalar(http, $"{baseUrl}/api/v1/query?query=count%28kube_pod_info%29", token); + double hasNex = await Scalar(http, $"{baseUrl}/api/v1/query?query=count%28node_cpu_seconds_total%29", token); + + double cpu, mem, nodes, rNode, pods, rPods, disk; + + if (hasNex > 0 && hasKsm > 0) + { + // Preferred: kube-prometheus-stack full stack (node-exporter + kube-state-metrics). + cpu = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)")}", token); + mem = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("100 - (avg(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100)")}", token); + nodes = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("count(kube_node_info)")}", token); + rNode = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("count(kube_node_status_condition{condition=\"Ready\",status=\"true\"})")}", token); + pods = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("count(kube_pod_info)")}", token); + rPods = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("sum(kube_pod_status_phase{phase=\"Running\"})")}", token); + disk = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("100 - (avg(node_filesystem_avail_bytes{mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"}) * 100)")}", token); + } + else + { + // Fallback: kubelet/cAdvisor metrics only (no kube-state-metrics or node-exporter). + // These are available from the kubelet job in any kube-prometheus-stack installation. + logger.LogInformation( + "kube-state-metrics/node-exporter not scraped — using kubelet/cAdvisor metrics for cluster health"); + + cpu = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("100 * sum(rate(container_cpu_usage_seconds_total{container!=\"\",namespace!=\"\"}[5m])) / sum(machine_cpu_cores)")}", token); + mem = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("100 * sum(container_memory_working_set_bytes{container!=\"\",namespace!=\"\"}) / sum(machine_memory_bytes)")}", token); + nodes = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("count(count by (node) (kubelet_running_pods))")}", token); + rNode = nodes; // kubelet_running_pods only reports healthy nodes + pods = await Scalar(http, $"{baseUrl}/api/v1/query?query={Q("sum(kubelet_running_pods)")}", token); + rPods = pods; + disk = 0; // not available without node-exporter + } + + return new ClusterHealthSummary + { + CpuUsagePercent = cpu, + MemoryUsagePercent = mem, + TotalNodes = (int)nodes, + ReadyNodes = (int)rNode, + TotalPods = (int)pods, + RunningPods = (int)rPods, + DiskUsagePercent = disk, + QueriedAt = DateTime.UtcNow + }; + }, + $"cluster health for {clusterId}", ct); } /// @@ -215,54 +296,23 @@ public class PrometheusService(IDbContextFactory dbFactory public async Task>> GetMetricRangeAsync( Guid clusterId, string query, TimeSpan duration, CancellationToken ct = default) { - using ApplicationDbContext db = dbFactory.CreateDbContext(); + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); - KubernetesCluster? cluster = await db.KubernetesClusters - .Include(c => c.Components) - .FirstOrDefaultAsync(c => c.Id == clusterId, ct); + long end = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + long start = end - (long)duration.TotalSeconds; + int step = Math.Max(15, (int)(duration.TotalSeconds / 100)); + string encodedQuery = Q(query); - if (cluster is null) - { - return KubernetesOperationResult>.Failure("Cluster not found."); - } - - if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) - { - return KubernetesOperationResult>.Failure("No kubeconfig."); - } - - ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => - c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); - - if (prometheusComponent is null) - { - return KubernetesOperationResult>.Failure("No prometheus component."); - } - - PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); - - try - { - Kubernetes client = CreateClient(cluster.Kubeconfig); - string baseUrl = $"/api/v1/namespaces/{config.Namespace}/services/{config.ServiceName}:{config.ServicePort}/proxy"; - - long end = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - long start = end - (long)duration.TotalSeconds; - int step = Math.Max(15, (int)(duration.TotalSeconds / 100)); - - string encodedQuery = Uri.EscapeDataString(query); - string url = $"{baseUrl}/api/v1/query_range?query={encodedQuery}&start={start}&end={end}&step={step}"; - - string json = await ProxyGetAsync(client, url, ct); - List series = ParseRangeQueryResult(json); - - return KubernetesOperationResult>.Success(series); - } - catch (Exception ex) - { - return KubernetesOperationResult>.Failure( - $"Range query failed: {ex.Message}"); - } + return await WithServiceAsync>( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync( + $"{baseUrl}/api/v1/query_range?query={encodedQuery}&start={start}&end={end}&step={step}", token); + return ParseRangeQueryResult(json); + }, + $"range query for {clusterId}", ct); } /// @@ -271,47 +321,17 @@ public class PrometheusService(IDbContextFactory dbFactory public async Task>> GetAlertsAsync( Guid clusterId, CancellationToken ct = default) { - using ApplicationDbContext db = dbFactory.CreateDbContext(); + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); - KubernetesCluster? cluster = await db.KubernetesClusters - .Include(c => c.Components) - .FirstOrDefaultAsync(c => c.Id == clusterId, ct); - - if (cluster is null) - { - return KubernetesOperationResult>.Failure("Cluster not found."); - } - - if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) - { - return KubernetesOperationResult>.Failure("No kubeconfig."); - } - - ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => - c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); - - if (prometheusComponent is null) - { - return KubernetesOperationResult>.Failure("No prometheus component."); - } - - PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); - - try - { - Kubernetes client = CreateClient(cluster.Kubeconfig); - string baseUrl = $"/api/v1/namespaces/{config.Namespace}/services/{config.AlertmanagerServiceName}:{config.AlertmanagerServicePort}/proxy"; - - string json = await ProxyGetAsync(client, $"{baseUrl}/api/v2/alerts", ct); - List alerts = ParseAlertmanagerAlerts(json); - - return KubernetesOperationResult>.Success(alerts); - } - catch (Exception ex) - { - return KubernetesOperationResult>.Failure( - $"Failed to query Alertmanager: {ex.Message}"); - } + return await WithServiceAsync>( + info.Kubeconfig, info.Config.Namespace, info.Config.AlertmanagerServiceName, info.Config.AlertmanagerServicePort, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}/api/v2/alerts", token); + return ParseAlertmanagerAlerts(json); + }, + $"alerts for {clusterId}", ct); } /// @@ -320,47 +340,17 @@ public class PrometheusService(IDbContextFactory dbFactory public async Task>> GetSilencesAsync( Guid clusterId, CancellationToken ct = default) { - using ApplicationDbContext db = dbFactory.CreateDbContext(); + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); - KubernetesCluster? cluster = await db.KubernetesClusters - .Include(c => c.Components) - .FirstOrDefaultAsync(c => c.Id == clusterId, ct); - - if (cluster is null) - { - return KubernetesOperationResult>.Failure("Cluster not found."); - } - - if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) - { - return KubernetesOperationResult>.Failure("No kubeconfig."); - } - - ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => - c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); - - if (prometheusComponent is null) - { - return KubernetesOperationResult>.Failure("No prometheus component."); - } - - PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); - - try - { - Kubernetes client = CreateClient(cluster.Kubeconfig); - string baseUrl = $"/api/v1/namespaces/{config.Namespace}/services/{config.AlertmanagerServiceName}:{config.AlertmanagerServicePort}/proxy"; - - string json = await ProxyGetAsync(client, $"{baseUrl}/api/v2/silences", ct); - List silences = ParseAlertmanagerSilences(json); - - return KubernetesOperationResult>.Success(silences); - } - catch (Exception ex) - { - return KubernetesOperationResult>.Failure( - $"Failed to query Alertmanager silences: {ex.Message}"); - } + return await WithServiceAsync>( + info.Kubeconfig, info.Config.Namespace, info.Config.AlertmanagerServiceName, info.Config.AlertmanagerServicePort, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}/api/v2/silences", token); + return ParseAlertmanagerSilences(json); + }, + $"silences for {clusterId}", ct); } /// @@ -370,61 +360,369 @@ public class PrometheusService(IDbContextFactory dbFactory Guid clusterId, string comment, string createdBy, TimeSpan duration, List matchers, CancellationToken ct = default) { - using ApplicationDbContext db = dbFactory.CreateDbContext(); + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult.Failure(error!); - KubernetesCluster? cluster = await db.KubernetesClusters - .Include(c => c.Components) - .FirstOrDefaultAsync(c => c.Id == clusterId, ct); - - if (cluster is null) + string body = JsonSerializer.Serialize(new { - return KubernetesOperationResult.Failure("Cluster not found."); - } + matchers = matchers.Select(m => new { name = m.Name, value = m.Value, isRegex = m.IsRegex, isEqual = m.IsEqual }).ToArray(), + startsAt = DateTime.UtcNow.ToString("o"), + endsAt = DateTime.UtcNow.Add(duration).ToString("o"), + createdBy, + comment + }); - if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) - { - return KubernetesOperationResult.Failure("No kubeconfig."); - } - - ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => - c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); - - if (prometheusComponent is null) - { - return KubernetesOperationResult.Failure("No prometheus component."); - } - - PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); - - try - { - Kubernetes client = CreateClient(cluster.Kubeconfig); - string baseUrl = $"/api/v1/namespaces/{config.Namespace}/services/{config.AlertmanagerServiceName}:{config.AlertmanagerServicePort}/proxy"; - - object silencePayload = new + var result = await WithServiceAsync( + info.Kubeconfig, info.Config.Namespace, info.Config.AlertmanagerServiceName, info.Config.AlertmanagerServicePort, + async (http, baseUrl, token) => { - matchers = matchers.Select(m => new - { - name = m.Name, - value = m.Value, - isRegex = m.IsRegex, - isEqual = m.IsEqual - }).ToArray(), - startsAt = DateTime.UtcNow.ToString("o"), - endsAt = DateTime.UtcNow.Add(duration).ToString("o"), - createdBy, - comment - }; + using StringContent content = new(body, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await http.PostAsync($"{baseUrl}/api/v2/silences", content, token); + response.EnsureSuccessStatusCode(); + return true; + }, + $"create silence for {clusterId}", ct); - string body = JsonSerializer.Serialize(silencePayload); - await ProxyPostAsync(client, $"{baseUrl}/api/v2/silences", body, ct); + return result.IsSuccess ? KubernetesOperationResult.Success() : KubernetesOperationResult.Failure(result.Error!); + } - return KubernetesOperationResult.Success(); - } - catch (Exception ex) + /// + /// Queries namespace-scoped CPU, memory, pod count, and restart metrics for a + /// specific app deployment. Requires kube-prometheus-stack on the target cluster. + /// + public async Task> GetDeploymentMetricsAsync( + Guid deploymentId, CancellationToken ct = default) + { + string kubeconfig; + PrometheusConfig config; + string ns; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) { - return KubernetesOperationResult.Failure($"Failed to create silence: {ex.Message}"); + AppDeployment? deployment = await db.AppDeployments + .Include(d => d.Cluster) + .ThenInclude(c => c.Components) + .FirstOrDefaultAsync(d => d.Id == deploymentId, ct); + + if (deployment is null) + return KubernetesOperationResult.Failure("Deployment not found."); + + if (string.IsNullOrWhiteSpace(deployment.Cluster.Kubeconfig)) + return KubernetesOperationResult.Failure("No kubeconfig configured."); + + ClusterComponent? prometheusComponent = deployment.Cluster.Components.FirstOrDefault(c => + c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); + + if (prometheusComponent is null) + return KubernetesOperationResult.Failure( + "No prometheus component found on this cluster."); + + kubeconfig = deployment.Cluster.Kubeconfig; + config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); + ns = deployment.Namespace; } + + return await WithServiceAsync( + kubeconfig, config.Namespace, config.ServiceName, config.ServicePort, + async (http, baseUrl, token) => + { + string cpuQ = Q($"sum(rate(container_cpu_usage_seconds_total{{namespace=\"{ns}\",container!=\"\"}}[5m]))"); + string memQ = Q($"sum(container_memory_working_set_bytes{{namespace=\"{ns}\",container!=\"\"}})"); + + double cpuVal = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={cpuQ}", token)); + double memBytes = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={memQ}", token)); + + // Try kube-state-metrics for pod/restart counts; fall back to cAdvisor-derived counts. + string ksmPodQ = Q($"count(kube_pod_info{{namespace=\"{ns}\"}})"); + double podCount = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={ksmPodQ}", token)); + double restarts = 0; + + if (podCount == 0) + { + // kube-state-metrics not available — count distinct pods from cAdvisor + string cAdvisorPodQ = Q($"count(count by (pod) (container_cpu_usage_seconds_total{{namespace=\"{ns}\",container!=\"\"}}))"); + podCount = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={cAdvisorPodQ}", token)); + } + else + { + // kube-state-metrics available — also get restart count + string restartQ = Q($"sum(kube_pod_container_status_restarts_total{{namespace=\"{ns}\"}})"); + restarts = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={restartQ}", token)); + } + + return new DeploymentMetricsSummary + { + Namespace = ns, + CpuCores = Math.Round(cpuVal, 4), + MemoryMiB = Math.Round(memBytes / (1024 * 1024), 1), + PodCount = (int)podCount, + RestartCount = (int)restarts, + QueriedAt = DateTime.UtcNow + }; + }, + $"deployment metrics {deploymentId}", ct); + } + + /// + /// Returns CPU%, memory%, and pod-count time series for a cluster, selecting + /// node-exporter/kube-state-metrics queries when available and falling back to + /// kubelet/cAdvisor metrics when they are not scraped by this Prometheus instance. + /// + public async Task> GetClusterMetricsHistoryAsync( + Guid clusterId, TimeSpan duration, CancellationToken ct = default) + { + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult.Failure(error!); + + long end = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + long start = end - (long)duration.TotalSeconds; + int step = Math.Max(15, (int)(duration.TotalSeconds / 100)); + + return await WithServiceAsync( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + static async Task Scalar(HttpClient h, string url, CancellationToken t) => + ExtractScalarValue(await h.GetStringAsync(url, t)); + + double hasNex = await Scalar(http, $"{baseUrl}/api/v1/query?query=count%28node_cpu_seconds_total%29", token); + double hasKsm = await Scalar(http, $"{baseUrl}/api/v1/query?query=count%28kube_pod_info%29", token); + + string cpuQ, memQ, podQ; + + if (hasNex > 0 && hasKsm > 0) + { + cpuQ = Q("100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)"); + memQ = Q("100 - (avg(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100)"); + podQ = Q("sum(kube_pod_status_phase{phase=\"Running\"})"); + } + else + { + cpuQ = Q("100 * sum(rate(container_cpu_usage_seconds_total{container!=\"\",namespace!=\"\"}[5m])) / sum(machine_cpu_cores)"); + memQ = Q("100 * sum(container_memory_working_set_bytes{container!=\"\",namespace!=\"\"}) / sum(machine_memory_bytes)"); + podQ = Q("sum(kubelet_running_pods)"); + } + + string cpuJson = await http.GetStringAsync($"{baseUrl}/api/v1/query_range?query={cpuQ}&start={start}&end={end}&step={step}", token); + string memJson = await http.GetStringAsync($"{baseUrl}/api/v1/query_range?query={memQ}&start={start}&end={end}&step={step}", token); + string podJson = await http.GetStringAsync($"{baseUrl}/api/v1/query_range?query={podQ}&start={start}&end={end}&step={step}", token); + + List cpuSeries = ParseRangeQueryResult(cpuJson); + List memSeries = ParseRangeQueryResult(memJson); + List podSeries = ParseRangeQueryResult(podJson); + + return new ClusterMetricsHistory( + cpuSeries.Count > 0 ? cpuSeries[0].DataPoints : [], + memSeries.Count > 0 ? memSeries[0].DataPoints : [], + podSeries.Count > 0 ? podSeries[0].DataPoints : []); + }, + $"metrics history for {clusterId}", ct); + } + + /// + /// Aggregate CPU, memory, and pod metrics for all deployments in a single app + /// by querying each cluster's Prometheus with a namespace regex filter. + /// + public async Task> GetAppMetricsAsync( + Guid appId, CancellationToken ct = default) + { + List<(string Kubeconfig, PrometheusConfig Config, string NsRegex)> clusterQueries; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + List deployments = await db.AppDeployments + .Include(d => d.Cluster) + .ThenInclude(c => c.Components) + .Where(d => d.AppId == appId && d.Cluster.Kubeconfig != null && d.Cluster.Kubeconfig != "") + .ToListAsync(ct); + + if (deployments.Count == 0) + return KubernetesOperationResult.Failure("No deployments with clusters configured for this app."); + + clusterQueries = deployments + .GroupBy(d => d.ClusterId) + .Select(g => + { + AppDeployment first = g.First(); + ClusterComponent? pc = first.Cluster.Components + .FirstOrDefault(c => c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); + PrometheusConfig cfg = pc is not null ? GetPrometheusConfig(pc) : new PrometheusConfig(); + string nsRegex = string.Join("|", g.Select(d => Regex.Escape(d.Namespace))); + return (first.Cluster.Kubeconfig!, cfg, nsRegex); + }) + .ToList(); + } + + return await AggregateMetricsAsync(clusterQueries, $"app metrics {appId}", ct); + } + + /// + /// Aggregate CPU, memory, and pod metrics across all apps belonging to a customer. + /// + public async Task> GetCustomerMetricsAsync( + Guid customerId, CancellationToken ct = default) + { + List<(string Kubeconfig, PrometheusConfig Config, string NsRegex)> clusterQueries; + + using (ApplicationDbContext db = dbFactory.CreateDbContext()) + { + List deployments = await db.AppDeployments + .Include(d => d.App) + .Include(d => d.Cluster) + .ThenInclude(c => c.Components) + .Where(d => d.App.CustomerId == customerId + && d.Cluster.Kubeconfig != null && d.Cluster.Kubeconfig != "") + .ToListAsync(ct); + + if (deployments.Count == 0) + return KubernetesOperationResult.Failure("No deployments with clusters configured for this customer."); + + clusterQueries = deployments + .GroupBy(d => d.ClusterId) + .Select(g => + { + AppDeployment first = g.First(); + ClusterComponent? pc = first.Cluster.Components + .FirstOrDefault(c => c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); + PrometheusConfig cfg = pc is not null ? GetPrometheusConfig(pc) : new PrometheusConfig(); + string nsRegex = string.Join("|", g.Select(d => Regex.Escape(d.Namespace)).Distinct()); + return (first.Cluster.Kubeconfig!, cfg, nsRegex); + }) + .ToList(); + } + + return await AggregateMetricsAsync(clusterQueries, $"customer metrics {customerId}", ct); + } + + /// + /// Retrieves CloudNativePG cluster metrics from Prometheus: replication lag, + /// backend count, active queries, and per-database sizes. + /// + public async Task> GetCnpgClusterMetricsAsync( + Guid clusterId, string cnpgClusterName, CancellationToken ct = default) + { + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult.Failure(error!); + + return await WithServiceAsync( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + string clusterLabel = $"{{cluster_name=\"{cnpgClusterName}\"}}"; + + string lagJson = await http.GetStringAsync( + $"{baseUrl}/api/v1/query?query={Q($"max(cnpg_pg_replication_lag{clusterLabel})")}", + token); + string backendsJson = await http.GetStringAsync( + $"{baseUrl}/api/v1/query?query={Q($"sum(cnpg_backends_total{clusterLabel})")}", + token); + string queriesJson = await http.GetStringAsync( + $"{baseUrl}/api/v1/query?query={Q($"sum(cnpg_pg_stat_activity_count{clusterLabel}{{state=\"active\"}})")}", + token); + string sizesJson = await http.GetStringAsync( + $"{baseUrl}/api/v1/query?query={Q($"cnpg_pg_database_size_bytes{clusterLabel}{{datname!=\"\"}}")}", + token); + + List lagResults = ParseInstantQueryResult(lagJson); + List backendResults = ParseInstantQueryResult(backendsJson); + List queryResults = ParseInstantQueryResult(queriesJson); + List sizeResults = ParseInstantQueryResult(sizesJson); + + return new CnpgMetricsSummary + { + ClusterName = cnpgClusterName, + ReplicationLagSeconds = lagResults.Count > 0 ? lagResults[0].Value : 0, + TotalBackends = backendResults.Count > 0 ? (int)backendResults[0].Value : 0, + ActiveQueries = queryResults.Count > 0 ? (int)queryResults[0].Value : 0, + DatabaseSizes = sizeResults + .Select(r => new CnpgDatabaseSize + { + DatabaseName = r.Labels.TryGetValue("datname", out string? dn) ? dn : "", + SizeMiB = r.Value / 1024.0 / 1024.0 + }) + .OrderByDescending(d => d.SizeMiB) + .ToList() + }; + }, + $"CNPG metrics for cluster {cnpgClusterName}", ct); + } + + /// + /// Retrieves all active scrape targets from Prometheus with their health (up/down). + /// + public async Task>> GetScrapeTargetsAsync( + Guid clusterId, CancellationToken ct = default) + { + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); + + return await WithServiceAsync>( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}/api/v1/targets?state=active", token); + return ParseScrapeTargets(json); + }, + $"scrape targets for cluster {clusterId}", ct); + } + + /// + /// Retrieves all alerting rules from Prometheus with their current evaluation state. + /// + public async Task>> GetAlertRulesAsync( + Guid clusterId, CancellationToken ct = default) + { + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult>.Failure(error!); + + return await WithServiceAsync>( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + string json = await http.GetStringAsync($"{baseUrl}/api/v1/rules?type=alert", token); + return ParseAlertRules(json); + }, + $"alert rules for cluster {clusterId}", ct); + } + + /// + /// Retrieves RabbitMQ metrics from Prometheus (via RabbitMQ Prometheus plugin). + /// + public async Task> GetRabbitMQMetricsAsync( + Guid clusterId, string rabbitMQName, CancellationToken ct = default) + { + var (info, error) = await ResolvePrometheusInfoAsync(clusterId, ct); + if (info is null) return KubernetesOperationResult.Failure(error!); + + return await WithServiceAsync( + info.Kubeconfig, info.Config.Namespace, info.Config.ServiceName, info.Config.ServicePort, + async (http, baseUrl, token) => + { + string label = $"{{rabbitmq_cluster=\"{rabbitMQName}\"}}"; + + string readyJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_identity_info{label})")}", token); + string msgJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_queue_messages{label})")}", token); + string readyMsgJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_queue_messages_ready{label})")}", token); + string unackJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_queue_messages_unacknowledged{label})")}", token); + string connJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_connections{label})")}", token); + string chanJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rabbitmq_channels{label})")}", token); + string pubRateJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rate(rabbitmq_queue_messages_published_total{label}[5m]))")}", token); + string conRateJson = await http.GetStringAsync($"{baseUrl}/api/v1/query?query={Q($"sum(rate(rabbitmq_queue_messages_delivered_total{label}[5m]))")}", token); + + return new RabbitMQMetricsSummary + { + ClusterName = rabbitMQName, + Nodes = (int)ExtractScalarValue(readyJson), + TotalMessages = (long)ExtractScalarValue(msgJson), + ReadyMessages = (long)ExtractScalarValue(readyMsgJson), + UnackedMessages = (long)ExtractScalarValue(unackJson), + Connections = (int)ExtractScalarValue(connJson), + Channels = (int)ExtractScalarValue(chanJson), + PublishRatePerSec = Math.Round(ExtractScalarValue(pubRateJson), 2), + DeliverRatePerSec = Math.Round(ExtractScalarValue(conRateJson), 2) + }; + }, + $"RabbitMQ metrics for {rabbitMQName}", ct); } // ──────── Static Parsing Methods ──────── @@ -588,16 +886,16 @@ public class PrometheusService(IDbContextFactory dbFactory ? d.GetString() ?? "" : ""; } - if (alert.TryGetProperty("startsAt", out JsonElement startsAt)) + if (alert.TryGetProperty("startsAt", out JsonElement startsAt) + && DateTimeOffset.TryParse(startsAt.GetString(), out DateTimeOffset parsedStart)) { - DateTime.TryParse(startsAt.GetString(), out DateTime parsed); - info.StartsAt = parsed; + info.StartsAt = parsedStart.UtcDateTime; } - if (alert.TryGetProperty("endsAt", out JsonElement endsAt)) + if (alert.TryGetProperty("endsAt", out JsonElement endsAt) + && DateTimeOffset.TryParse(endsAt.GetString(), out DateTimeOffset parsedEnd)) { - DateTime.TryParse(endsAt.GetString(), out DateTime parsed); - info.EndsAt = parsed; + info.EndsAt = parsedEnd.UtcDateTime; } if (alert.TryGetProperty("status", out JsonElement status) @@ -663,16 +961,16 @@ public class PrometheusService(IDbContextFactory dbFactory info.CreatedBy = createdBy.GetString() ?? ""; } - if (silence.TryGetProperty("startsAt", out JsonElement startsAt)) + if (silence.TryGetProperty("startsAt", out JsonElement startsAt) + && DateTimeOffset.TryParse(startsAt.GetString(), out DateTimeOffset parsedStart)) { - DateTime.TryParse(startsAt.GetString(), out DateTime parsed); - info.StartsAt = parsed; + info.StartsAt = parsedStart.UtcDateTime; } - if (silence.TryGetProperty("endsAt", out JsonElement endsAt)) + if (silence.TryGetProperty("endsAt", out JsonElement endsAt) + && DateTimeOffset.TryParse(endsAt.GetString(), out DateTimeOffset parsedEnd)) { - DateTime.TryParse(endsAt.GetString(), out DateTime parsed); - info.EndsAt = parsed; + info.EndsAt = parsedEnd.UtcDateTime; } if (silence.TryGetProperty("matchers", out JsonElement matchers) @@ -703,58 +1001,395 @@ public class PrometheusService(IDbContextFactory dbFactory } /// - /// Extracts Prometheus configuration from a component's Configuration JSON. - /// Falls back to standard kube-prometheus-stack defaults if parsing fails. + /// Extracts Prometheus configuration for a component, deriving sensible defaults + /// from the component's own metadata (ReleaseName, Namespace) when no explicit + /// JSON configuration is stored. + /// + /// For kube-prometheus-stack, the Helm release name determines the service names: + /// Prometheus: {releaseName}-kube-prometheus-prometheus + /// Alertmanager: {releaseName}-kube-prometheus-alertmanager + /// + /// An explicit Configuration JSON always wins over the derived defaults. /// - public static PrometheusConfig? GetPrometheusConfig(ClusterComponent component) + public static PrometheusConfig GetPrometheusConfig(ClusterComponent component) { - if (string.IsNullOrWhiteSpace(component.Configuration)) + // Derive defaults from component metadata so they work regardless of release name. + string releaseName = component.ReleaseName ?? component.Name; + string ns = component.Namespace ?? "monitoring"; + + PrometheusConfig derived = new() { - return new PrometheusConfig(); - } + Namespace = ns, + ServiceName = $"{releaseName}-prometheus", + AlertmanagerServiceName = $"{releaseName}-alertmanager", + }; + + if (string.IsNullOrWhiteSpace(component.Configuration)) + return derived; try { - PrometheusConfig? config = JsonSerializer.Deserialize( + PrometheusConfig? explicit_ = JsonSerializer.Deserialize( component.Configuration, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - return config ?? new PrometheusConfig(); + if (explicit_ is null) return derived; + + // Merge: only override a derived field when the JSON has a non-default value. + if (!string.IsNullOrWhiteSpace(explicit_.Namespace)) + derived.Namespace = explicit_.Namespace; + if (!string.IsNullOrWhiteSpace(explicit_.ServiceName)) + derived.ServiceName = explicit_.ServiceName; + if (explicit_.ServicePort != 9090) + derived.ServicePort = explicit_.ServicePort; + if (!string.IsNullOrWhiteSpace(explicit_.AlertmanagerServiceName)) + derived.AlertmanagerServiceName = explicit_.AlertmanagerServiceName; + if (explicit_.AlertmanagerServicePort != 9093) + derived.AlertmanagerServicePort = explicit_.AlertmanagerServicePort; + + return derived; } catch { - return new PrometheusConfig(); + return derived; } } + /// + /// Queries each cluster/namespace-regex pair, sums CPU, memory, pods across all clusters, + /// and returns a single aggregated DeploymentMetricsSummary. + /// + private async Task> AggregateMetricsAsync( + IEnumerable<(string Kubeconfig, PrometheusConfig Config, string NsRegex)> clusterQueries, + string logContext, + CancellationToken ct) + { + double totalCpu = 0, totalMemBytes = 0, totalPods = 0, totalRestarts = 0; + bool anySuccess = false; + + foreach ((string kubeconfig, PrometheusConfig cfg, string nsRegex) in clusterQueries) + { + KubernetesOperationResult<(double, double, double, double)> result = + await WithServiceAsync<(double, double, double, double)>( + kubeconfig, cfg.Namespace, cfg.ServiceName, cfg.ServicePort, + async (http, baseUrl, token) => + { + string cpuQ = Q($"sum(rate(container_cpu_usage_seconds_total{{namespace=~\"{nsRegex}\",container!=\"\"}}[5m]))"); + string memQ = Q($"sum(container_memory_working_set_bytes{{namespace=~\"{nsRegex}\",container!=\"\"}})"); + string podQ = Q($"count(count by (pod, namespace) (container_cpu_usage_seconds_total{{namespace=~\"{nsRegex}\",container!=\"\"}}))"); + string rstQ = Q($"sum(kube_pod_container_status_restarts_total{{namespace=~\"{nsRegex}\"}})"); + + double cpu = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={cpuQ}", token)); + double mem = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={memQ}", token)); + double pods = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={podQ}", token)); + double rst = ExtractScalarValue(await http.GetStringAsync($"{baseUrl}/api/v1/query?query={rstQ}", token)); + + return (cpu, mem, pods, rst); + }, + logContext, ct); + + if (result.IsSuccess) + { + anySuccess = true; + totalCpu += result.Data.Item1; + totalMemBytes += result.Data.Item2; + totalPods += result.Data.Item3; + totalRestarts += result.Data.Item4; + } + } + + if (!anySuccess) + return KubernetesOperationResult.Failure( + "Could not reach Prometheus on any cluster for this resource."); + + return KubernetesOperationResult.Success(new DeploymentMetricsSummary + { + Namespace = "aggregated", + CpuCores = Math.Round(totalCpu, 4), + MemoryMiB = Math.Round(totalMemBytes / (1024 * 1024), 1), + PodCount = (int)totalPods, + RestartCount = (int)totalRestarts, + QueriedAt = DateTime.UtcNow + }); + } + // ──────── Internal Helpers ──────── - private static double ExtractScalarValue(string json) + private sealed record ResolvedPrometheusInfo(string Kubeconfig, PrometheusConfig Config); + + private async Task<(ResolvedPrometheusInfo? Info, string? Error)> ResolvePrometheusInfoAsync( + Guid clusterId, CancellationToken ct) { - List results = ParseInstantQueryResult(json); - return results.Count > 0 ? results[0].Value : 0; + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster? cluster = await db.KubernetesClusters + .Include(c => c.Components) + .FirstOrDefaultAsync(c => c.Id == clusterId, ct); + + if (cluster is null) + return (null, "Cluster not found."); + + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return (null, "No kubeconfig configured for this cluster."); + + ClusterComponent? prometheusComponent = cluster.Components.FirstOrDefault(c => + c.Name.Contains("prometheus", StringComparison.OrdinalIgnoreCase)); + + if (prometheusComponent is null) + return (null, "No prometheus component found on this cluster."); + + PrometheusConfig config = GetPrometheusConfig(prometheusComponent) ?? new PrometheusConfig(); + return (new ResolvedPrometheusInfo(cluster.Kubeconfig, config), null); } - private static Kubernetes CreateClient(string kubeconfig) + /// + /// Routes HTTP requests to an in-cluster service via the Kubernetes API server's + /// pod proxy endpoint: /api/v1/namespaces/{ns}/pods/{pod}:{port}/proxy/{path}. + /// + /// This is equivalent to what "kubectl proxy" does — no WebSocket, no local TCP + /// listener, no subprocess. The k8s client's HttpClient already carries authentication + /// (Bearer token / client cert), so the same credentials that work for API calls + /// also work for the pod proxy. + /// + /// Requires get permission on pods/proxy in the pod's namespace, which is present + /// in any standard cluster-admin or operator kubeconfig. + /// + private async Task> WithServiceAsync( + string kubeconfig, + string ns, + string svcName, + int svcPort, + Func> action, + string logContext, + CancellationToken ct) + { + try + { + using Kubernetes k8s = CreateK8sClient(kubeconfig); + + V1EndpointAddress? addr = await FindEndpointAddressAsync(k8s, ns, svcName, svcPort, ct); + if (addr?.TargetRef is null) + throw new InvalidOperationException( + $"No ready pods found for service {svcName}:{svcPort} in {ns}. " + + "Check the component's ReleaseName and Namespace, or set an explicit Configuration JSON."); + + string podName = addr.TargetRef.Name; + string podNs = addr.TargetRef.NamespaceProperty ?? ns; + + // Build the pod-proxy base URL. All requests go: + // app → k8s API server → pod:{svcPort} + // Authentication is already wired into k8s.HttpClient. + string baseUrl = k8s.BaseUri.ToString().TrimEnd('/') + + $"/api/v1/namespaces/{podNs}/pods/{podName}:{svcPort}/proxy"; + + logger.LogDebug("Prometheus proxy → pod {Pod} ({PodNs}) at {BaseUrl}", podName, podNs, baseUrl); + + await VerifyPrometheusConnectionAsync(k8s.HttpClient, baseUrl, ct); + + T result = await action(k8s.HttpClient, baseUrl, ct); + return KubernetesOperationResult.Success(result); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Prometheus query failed ({Context})", logContext); + return KubernetesOperationResult.Failure(ex.Message); + } + } + + /// + /// Probes /-/healthy. Not all services expose this (some Alertmanager versions don't), + /// so a 404 is treated as "endpoint absent, continue anyway" rather than a hard failure. + /// Any other non-2xx (401, 403, 503 …) is a real error and is re-thrown. + /// + private async Task VerifyPrometheusConnectionAsync(HttpClient http, string baseUrl, CancellationToken ct) + { + try + { + string body = await http.GetStringAsync($"{baseUrl}/-/healthy", ct); + logger.LogDebug("Health check response from {BaseUrl}: {Body}", baseUrl, body); + } + catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + logger.LogDebug("/-/healthy not found on {BaseUrl} — skipping health probe", baseUrl); + } + } + + /// + /// Finds a ready pod backing the given service. First tries the configured service + /// name directly. If the endpoint resource is not found (wrong release name, different + /// namespace), falls back to listing all endpoints in the namespace and finding one + /// whose name contains "prometheus" (or "alertmanager") and exposes the expected port. + /// Logs what it found so operators can tune the component configuration. + /// + private async Task FindEndpointAddressAsync( + Kubernetes k8s, string ns, string svcName, int svcPort, CancellationToken ct) + { + // 1. Try the configured/derived name first. + try + { + V1Endpoints ep = await k8s.CoreV1.ReadNamespacedEndpointsAsync(svcName, ns, cancellationToken: ct); + V1EndpointAddress? direct = ReadyPodAddress(ep, svcPort); + if (direct is not null) return direct; + } + catch (k8s.Autorest.HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound) { } + + // 2. Fall back: scan all endpoints in the namespace for a Prometheus-looking one. + logger.LogWarning( + "Endpoint {SvcName} not found in {Ns} — scanning namespace for a matching service on port {Port}", + svcName, ns, svcPort); + + V1EndpointsList all = await k8s.CoreV1.ListNamespacedEndpointsAsync(ns, cancellationToken: ct); + + // Match by port first (most specific), then by name containing the right keyword. + string keyword = svcName.Contains("alertmanager", StringComparison.OrdinalIgnoreCase) + ? "alertmanager" : "prometheus"; + + foreach (V1Endpoints candidate in all.Items.OrderBy(e => e.Metadata.Name)) + { + if (!candidate.Metadata.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase)) continue; + + V1EndpointAddress? addr = ReadyPodAddress(candidate, svcPort); + if (addr is null) continue; + + logger.LogInformation( + "Auto-discovered {Keyword} endpoint {Found} (configured name was {Configured}). " + + "Set the component's Configuration JSON to fix this permanently.", + keyword, candidate.Metadata.Name, svcName); + + return addr; + } + + return null; + } + + private static V1EndpointAddress? ReadyPodAddress(V1Endpoints ep, int port) + { + if (ep.Subsets is null) return null; + + foreach (V1EndpointSubset subset in ep.Subsets) + { + bool portMatches = subset.Ports?.Any(p => p.Port == port) ?? true; + if (!portMatches) continue; + + V1EndpointAddress? addr = (subset.Addresses ?? []) + .FirstOrDefault(a => a.TargetRef?.Kind == "Pod"); + if (addr is not null) return addr; + } + + return null; + } + + /// + /// Parses a Prometheus /api/v1/targets JSON response into ScrapeTarget objects. + /// + public static List ParseScrapeTargets(string json) + { + List results = []; + try + { + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + + if (root.GetProperty("status").GetString() != "success") return results; + + JsonElement activeTargets = root.GetProperty("data").GetProperty("activeTargets"); + foreach (JsonElement t in activeTargets.EnumerateArray()) + { + ScrapeTarget target = new() + { + Pool = t.TryGetProperty("scrapePool", out JsonElement pool) ? pool.GetString() ?? "" : "", + ScrapeUrl = t.TryGetProperty("scrapeUrl", out JsonElement url) ? url.GetString() ?? "" : "", + Health = t.TryGetProperty("health", out JsonElement h) ? h.GetString() ?? "" : "", + LastError = t.TryGetProperty("lastError", out JsonElement le) ? le.GetString() ?? "" : "", + LastScrapeDurationSeconds = t.TryGetProperty("lastScrapeDuration", out JsonElement ld) ? ld.GetDouble() : 0 + }; + + if (t.TryGetProperty("lastScrape", out JsonElement ls) && ls.ValueKind == JsonValueKind.String + && DateTimeOffset.TryParse(ls.GetString(), out DateTimeOffset scraped)) + target.LastScrape = scraped.UtcDateTime; + + if (t.TryGetProperty("labels", out JsonElement labels)) + foreach (JsonProperty p in labels.EnumerateObject()) + target.Labels[p.Name] = p.Value.GetString() ?? ""; + + results.Add(target); + } + } + catch { } + return results; + } + + /// + /// Parses a Prometheus /api/v1/rules?type=alert JSON response into AlertRule objects. + /// + public static List ParseAlertRules(string json) + { + List results = []; + try + { + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + + if (root.GetProperty("status").GetString() != "success") return results; + + JsonElement groups = root.GetProperty("data").GetProperty("groups"); + foreach (JsonElement group in groups.EnumerateArray()) + { + string groupName = group.TryGetProperty("name", out JsonElement gn) ? gn.GetString() ?? "" : ""; + + if (!group.TryGetProperty("rules", out JsonElement rules)) continue; + + foreach (JsonElement rule in rules.EnumerateArray()) + { + if (!rule.TryGetProperty("type", out JsonElement typeEl) || typeEl.GetString() != "alerting") continue; + + AlertRule r = new() + { + GroupName = groupName, + Name = rule.TryGetProperty("name", out JsonElement n) ? n.GetString() ?? "" : "", + Query = rule.TryGetProperty("query", out JsonElement q) ? q.GetString() ?? "" : "", + State = rule.TryGetProperty("state", out JsonElement st) ? st.GetString() ?? "" : "", + EvaluationTimeSeconds = rule.TryGetProperty("evaluationTime", out JsonElement et) ? et.GetDouble() : 0 + }; + + if (rule.TryGetProperty("duration", out JsonElement dur)) + r.DurationSeconds = dur.GetDouble(); + + if (rule.TryGetProperty("labels", out JsonElement labels)) + foreach (JsonProperty p in labels.EnumerateObject()) + { + r.Labels[p.Name] = p.Value.GetString() ?? ""; + if (p.Name == "severity") r.Severity = p.Value.GetString() ?? ""; + } + + if (rule.TryGetProperty("annotations", out JsonElement annotations)) + foreach (JsonProperty p in annotations.EnumerateObject()) + { + r.Annotations[p.Name] = p.Value.GetString() ?? ""; + if (p.Name == "summary") r.Summary = p.Value.GetString() ?? ""; + if (p.Name == "runbook_url") r.RunbookUrl = p.Value.GetString() ?? ""; + } + + results.Add(r); + } + } + } + catch { } + return results; + } + + private static Kubernetes CreateK8sClient(string kubeconfig) { using MemoryStream stream = new(Encoding.UTF8.GetBytes(kubeconfig)); KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream); return new Kubernetes(config); } - private static async Task ProxyGetAsync(Kubernetes client, string path, CancellationToken ct) - { - // Use the K8s API server proxy to reach in-cluster services. + private static string Q(string promQuery) => Uri.EscapeDataString(promQuery); - Uri requestUri = new(client.BaseUri, path); - HttpResponseMessage response = await client.HttpClient.GetAsync(requestUri, ct); - return await response.Content.ReadAsStringAsync(ct); - } - - private static async Task ProxyPostAsync(Kubernetes client, string path, string body, CancellationToken ct) + private static double ExtractScalarValue(string json) { - Uri requestUri = new(client.BaseUri, path); - StringContent content = new(body, Encoding.UTF8, "application/json"); - await client.HttpClient.PostAsync(requestUri, content, ct); + List results = ParseInstantQueryResult(json); + return results.Count > 0 ? results[0].Value : 0; } } diff --git a/src/EntKube.Web/Services/RabbitMQService.cs b/src/EntKube.Web/Services/RabbitMQService.cs new file mode 100644 index 0000000..888dde7 --- /dev/null +++ b/src/EntKube.Web/Services/RabbitMQService.cs @@ -0,0 +1,1348 @@ +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using Amazon.S3; +using Amazon.S3.Model; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +// ── DTOs ───────────────────────────────────────────────────────────────────── + +public class RabbitMQClusterInfo +{ + public required string Name { get; set; } + public required string Namespace { get; set; } + public bool AllReplicasReady { get; set; } + public bool ClusterAvailable { get; set; } + public int ReadyReplicas { get; set; } +} + +public class RabbitMQVhostInfo +{ + public required string K8sName { get; set; } + public required string VhostName { get; set; } +} + +public class RabbitMQQueueInfo +{ + public required string K8sName { get; set; } + public required string QueueName { get; set; } + public required string Vhost { get; set; } + public string QueueType { get; set; } = "quorum"; + public bool Durable { get; set; } = true; + public bool AutoDelete { get; set; } +} + +public class RabbitMQExchangeInfo +{ + public required string K8sName { get; set; } + public required string ExchangeName { get; set; } + public required string Vhost { get; set; } + public string ExchangeType { get; set; } = "direct"; + public bool Durable { get; set; } = true; + public bool AutoDelete { get; set; } +} + +public class RabbitMQRoutingBindingInfo +{ + public required string K8sName { get; set; } + public required string Source { get; set; } + public required string Destination { get; set; } + public required string DestinationType { get; set; } + public required string Vhost { get; set; } + public string RoutingKey { get; set; } = ""; +} + +public class RabbitMQUserInfo +{ + public required string K8sName { get; set; } + public required string Username { get; set; } + public List Tags { get; set; } = []; +} + +// kept for backward compat with any existing callers +public class RabbitMQLiveTopology +{ + public List Vhosts { get; set; } = []; + public List Queues { get; set; } = []; + public List Exchanges { get; set; } = []; + public List Users { get; set; } = []; +} + +public class RabbitMQOperatorStatus +{ + public bool ClusterOperatorAvailable { get; set; } + public bool TopologyOperatorAvailable { get; set; } + public string? ClusterOperatorClusterName { get; set; } + public string? TopologyOperatorClusterName { get; set; } +} + +// ── Service ─────────────────────────────────────────────────────────────────── + +/// +/// Manages RabbitMQ clusters and their definitions backups within EntKube. +/// +/// Cluster infrastructure is handled by the RabbitMQ Cluster Operator (RabbitmqCluster CRD). +/// Messaging topology (vhosts, queues, exchanges, bindings) is handled by the RabbitMQ +/// Messaging Topology Operator and discovered live from Kubernetes — EntKube does not +/// own topology lifecycle, only surfaces it for visibility. +/// +/// Backup/restore exports the full broker definitions.json via rabbitmqctl and stores it +/// in S3, mirroring the Keycloak realm export pattern. +/// +public class RabbitMQService( + IDbContextFactory dbFactory, + IKubernetesClientFactory k8s, + VaultService vaultService) +{ + // ── Cluster queries ─────────────────────────────────────────────────────── + + public async Task> GetClustersAsync(Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RabbitMQClusters + .Include(c => c.KubernetesCluster).ThenInclude(k => k.Environment) + .Include(c => c.StorageLink) + .Where(c => c.TenantId == tenantId) + .OrderBy(c => c.Name) + .ToListAsync(ct); + } + + public async Task GetClusterAsync(Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RabbitMQClusters + .Include(c => c.KubernetesCluster).ThenInclude(k => k.Environment) + .Include(c => c.StorageLink) + .Include(c => c.Backups.OrderByDescending(b => b.CreatedAt)) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct); + } + + // ── Operator detection ──────────────────────────────────────────────────── + + /// + /// Detects whether the RabbitMQ Cluster Operator and Topology Operator are installed + /// on any of the tenant's Kubernetes clusters by querying installed ClusterComponents. + /// Follows the same pattern as DatabaseService.GetOperatorStatusAsync. + /// + public async Task GetOperatorStatusAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List installed = await db.ClusterComponents + .Include(c => c.Cluster) + .Where(c => c.Cluster.TenantId == tenantId + && c.Status == ComponentStatus.Installed + && (c.Name == "rabbitmq-cluster-operator" + || c.ReleaseName == "rabbitmq-cluster-operator" + || c.Name == "rabbitmq-messaging-topology-operator" + || c.ReleaseName == "rabbitmq-topology-operator" + || c.ReleaseName == "rabbitmq-messaging-topology-operator")) + .ToListAsync(ct); + + ClusterComponent? clusterOp = installed.FirstOrDefault( + c => c.Name == "rabbitmq-cluster-operator" + || c.ReleaseName == "rabbitmq-cluster-operator"); + + ClusterComponent? topologyOp = installed.FirstOrDefault( + c => c.Name == "rabbitmq-messaging-topology-operator" + || c.ReleaseName is "rabbitmq-topology-operator" or "rabbitmq-messaging-topology-operator"); + + return new RabbitMQOperatorStatus + { + ClusterOperatorAvailable = clusterOp is not null, + TopologyOperatorAvailable = topologyOp is not null, + ClusterOperatorClusterName = clusterOp?.Cluster.Name, + TopologyOperatorClusterName = topologyOp?.Cluster.Name + }; + } + + // ── Cluster lifecycle ───────────────────────────────────────────────────── + + public async Task CreateClusterAsync( + Guid tenantId, + Guid kubernetesClusterId, + string name, + string ns, + string version, + int replicas, + string storageSize, + string? storageClass, + Guid? storageLinkId, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + if (await db.RabbitMQClusters.AnyAsync( + c => c.KubernetesClusterId == kubernetesClusterId && c.Name == name && c.Namespace == ns, ct)) + throw new InvalidOperationException($"A RabbitMQ cluster named '{name}' already exists in namespace '{ns}'."); + + RabbitMQCluster cluster = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KubernetesClusterId = kubernetesClusterId, + Name = name, + Namespace = ns, + RabbitMQVersion = version, + Replicas = replicas, + StorageSize = storageSize, + StorageClass = storageClass, + StorageLinkId = storageLinkId, + Status = RabbitMQClusterStatus.Creating + }; + + db.RabbitMQClusters.Add(cluster); + await db.SaveChangesAsync(ct); + + try + { + KubernetesCluster k8sCluster = await db.KubernetesClusters + .FirstAsync(c => c.Id == kubernetesClusterId, ct); + string kubeconfig = k8sCluster.Kubeconfig!; + + await k8s.EnsureNamespaceAsync(ns, kubeconfig, ct); + await k8s.ApplyManifestAsync(BuildClusterManifest(cluster), kubeconfig, ct); + + cluster.Status = RabbitMQClusterStatus.Running; + } + catch (Exception ex) + { + cluster.Status = RabbitMQClusterStatus.Failed; + cluster.LastError = ex.Message; + } + + using ApplicationDbContext db2 = dbFactory.CreateDbContext(); + db2.RabbitMQClusters.Update(cluster); + await db2.SaveChangesAsync(ct); + + // Best-effort: sync generated admin credentials to vault immediately. + // The operator may not have provisioned the secret yet, so failures are silently ignored. + // The operator can retry via the "Sync from K8s" button in the credentials tab. + if (cluster.Status == RabbitMQClusterStatus.Running) + { + try { await SyncCredentialsToVaultAsync(tenantId, cluster.Id, ct); } + catch { } + } + + return cluster; + } + + public async Task DeleteClusterAsync(Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + cluster.Status = RabbitMQClusterStatus.Deleting; + await db.SaveChangesAsync(ct); + + try + { + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + await k8s.DeleteManifestAsync("rabbitmqcluster", cluster.Name, cluster.Namespace, kubeconfig, ct); + } + catch (Exception ex) + { + cluster.Status = RabbitMQClusterStatus.Failed; + cluster.LastError = ex.Message; + await db.SaveChangesAsync(ct); + throw; + } + + db.RabbitMQClusters.Remove(cluster); + await db.SaveChangesAsync(ct); + } + + // ── Live K8s status ─────────────────────────────────────────────────────── + + public async Task GetLiveStatusAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + try + { + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + string json = await k8s.GetJsonAsync( + $"rabbitmqcluster/{cluster.Name}", cluster.Namespace, kubeconfig, ct: ct); + + return ParseClusterStatus(json, cluster.Name, cluster.Namespace); + } + catch + { + return null; + } + } + + /// + /// Reconciles the EntKube status of a cluster against live K8s state. + /// Call from a background poll or after user-triggered refresh. + /// + public async Task ReconcileStatusAsync(Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQClusterInfo? info = await GetLiveStatusAsync(tenantId, clusterId, ct); + if (info is null) return; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .FirstOrDefaultAsync(c => c.Id == clusterId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + if (info.ClusterAvailable && info.AllReplicasReady) + { + cluster.Status = RabbitMQClusterStatus.Running; + cluster.LastError = null; + } + else if (cluster.Status != RabbitMQClusterStatus.Creating) + { + cluster.Status = RabbitMQClusterStatus.Failed; + } + + await db.SaveChangesAsync(ct); + } + + // ── Live topology (read from K8s topology operator CRDs) ────────────────── + + public async Task GetLiveTopologyAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + string labelSelector = $"rabbitmq.com/cluster={cluster.Name}"; + + RabbitMQLiveTopology topology = new(); + + try + { + topology.Vhosts = await GetTopologyCrdNamesAsync( + "vhosts.rabbitmq.com", cluster.Namespace, kubeconfig, labelSelector, ct); + } + catch { /* operator not installed or no resources */ } + + try + { + topology.Queues = await GetTopologyCrdNamesAsync( + "queues.rabbitmq.com", cluster.Namespace, kubeconfig, labelSelector, ct); + } + catch { } + + try + { + topology.Exchanges = await GetTopologyCrdNamesAsync( + "exchanges.rabbitmq.com", cluster.Namespace, kubeconfig, labelSelector, ct); + } + catch { } + + try + { + topology.Users = await GetTopologyCrdNamesAsync( + "users.rabbitmq.com", cluster.Namespace, kubeconfig, labelSelector, ct); + } + catch { } + + return topology; + } + + // ── Admin credentials ───────────────────────────────────────────────────── + + /// + /// Reads the admin username and password from the K8s Secret created by the cluster operator. + /// The operator always names this secret {cluster-name}-default-user. + /// + public async Task<(string Username, string Password)?> GetAdminCredentialsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + string secretName = $"{cluster.Name}-default-user"; + + string? username = await k8s.GetSecretValueAsync(secretName, "username", cluster.Namespace, kubeconfig, ct); + string? password = await k8s.GetSecretValueAsync(secretName, "password", cluster.Namespace, kubeconfig, ct); + + if (username is null || password is null) return null; + return (username, password); + } + + // ── Credential vault sync ───────────────────────────────────────────────── + + /// + /// Reads the admin credentials from the operator-created K8s secret and stores + /// them encrypted in the tenant vault. Safe to call multiple times — overwrites + /// the previous values. Throws if the K8s secret is not yet available. + /// + public async Task SyncCredentialsToVaultAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + (string Username, string Password)? creds = await GetAdminCredentialsAsync(tenantId, clusterId, ct); + + if (creds is null) + throw new InvalidOperationException( + "The operator secret is not yet available. The cluster may still be initializing."); + + await vaultService.SetRabbitMQClusterSecretAsync(tenantId, clusterId, "RABBITMQ_USERNAME", creds.Value.Username, ct); + await vaultService.SetRabbitMQClusterSecretAsync(tenantId, clusterId, "RABBITMQ_PASSWORD", creds.Value.Password, ct); + } + + /// + /// Returns admin credentials from the vault (encrypted-at-rest). + /// Returns null if they have not been synced yet. + /// + public async Task<(string Username, string Password)?> GetVaultCredentialsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + string? username = await vaultService.GetRabbitMQClusterSecretValueAsync( + tenantId, clusterId, "RABBITMQ_USERNAME", ct); + string? password = await vaultService.GetRabbitMQClusterSecretValueAsync( + tenantId, clusterId, "RABBITMQ_PASSWORD", ct); + + if (username is null || password is null) return null; + return (username, password); + } + + // ── Backup / Restore ────────────────────────────────────────────────────── + + public async Task> GetBackupsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RabbitMQBackups + .Include(b => b.StorageLink) + .Where(b => b.TenantId == tenantId && b.RabbitMQClusterId == clusterId) + .OrderByDescending(b => b.CreatedAt) + .ToListAsync(ct); + } + + /// + /// Exports the full broker definitions (vhosts, exchanges, queues, bindings, policies, + /// users, permissions) via rabbitmqctl on the primary pod and uploads to S3. + /// + public async Task CreateBackupAsync( + Guid tenantId, Guid clusterId, Guid storageLinkId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQCluster cluster = await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + + StorageLink storageLink = await db.StorageLinks + .FirstOrDefaultAsync(s => s.Id == storageLinkId && s.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Storage link not found."); + + RabbitMQBackup backup = new() + { + Id = Guid.NewGuid(), + RabbitMQClusterId = clusterId, + TenantId = tenantId, + StorageLinkId = storageLinkId, + ObjectKey = $"rabbitmq/{cluster.Name}/{DateTime.UtcNow:yyyyMMdd-HHmmss}.json", + ClusterName = cluster.Name, + Status = RabbitMQBackupStatus.Creating + }; + + db.RabbitMQBackups.Add(backup); + await db.SaveChangesAsync(ct); + + try + { + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + string primaryPod = $"{cluster.Name}-server-0"; + + // rabbitmqctl export_definitions - writes the JSON definitions to stdout. + string json = await k8s.RunCommandOnPodAsync( + primaryPod, cluster.Namespace, + ["rabbitmqctl", "export_definitions", "-"], + kubeconfig, ct: ct); + + byte[] data = Encoding.UTF8.GetBytes(json); + await UploadToS3Async(tenantId, storageLink, backup.ObjectKey, data, ct); + + backup.SizeBytes = data.Length; + backup.Status = RabbitMQBackupStatus.Ready; + backup.CompletedAt = DateTime.UtcNow; + } + catch (Exception ex) + { + backup.Status = RabbitMQBackupStatus.Failed; + backup.LastError = ex.Message; + } + + using ApplicationDbContext db2 = dbFactory.CreateDbContext(); + db2.RabbitMQBackups.Update(backup); + await db2.SaveChangesAsync(ct); + + // Prune oldest backups if over the limit. + await PruneBackupsAsync(tenantId, clusterId, cluster.MaxBackups, ct); + + return backup; + } + + /// + /// Restores broker definitions from an S3 backup into the target cluster. + /// Pipes the definitions JSON to rabbitmqctl import_definitions - on the primary pod. + /// Existing topology that conflicts with the definitions will be overwritten. + /// + public async Task RestoreBackupAsync( + Guid tenantId, Guid backupId, Guid? targetClusterId = null, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQBackup backup = await db.RabbitMQBackups + .Include(b => b.Cluster).ThenInclude(c => c.KubernetesCluster) + .Include(b => b.StorageLink) + .FirstOrDefaultAsync(b => b.Id == backupId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Backup not found."); + + if (backup.StorageLink is null) + throw new InvalidOperationException("The storage link for this backup has been deleted and cannot be restored."); + + // Allow restoring into a different cluster (cross-environment migration). + RabbitMQCluster cluster = targetClusterId.HasValue + ? await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == targetClusterId.Value && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Target cluster not found.") + : backup.Cluster; + + byte[] data = await DownloadFromS3Async(tenantId, backup.StorageLink, backup.ObjectKey, ct); + string json = Encoding.UTF8.GetString(data); + + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + string primaryPod = $"{cluster.Name}-server-0"; + + // rabbitmqctl import_definitions - reads JSON from stdin. + await k8s.RunCommandOnPodWithStdinAsync( + primaryPod, cluster.Namespace, + ["rabbitmqctl", "import_definitions", "-"], + json, kubeconfig, ct); + } + + public async Task DeleteBackupAsync(Guid tenantId, Guid backupId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RabbitMQBackup backup = await db.RabbitMQBackups + .Include(b => b.StorageLink) + .FirstOrDefaultAsync(b => b.Id == backupId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Backup not found."); + + if (backup.StorageLink is not null) + { + try { await DeleteFromS3Async(tenantId, backup.StorageLink, backup.ObjectKey, ct); } + catch { /* best-effort; remove the DB record even if S3 delete fails */ } + } + + db.RabbitMQBackups.Remove(backup); + await db.SaveChangesAsync(ct); + } + + // ── Topology — Vhosts ───────────────────────────────────────────────────── + + public async Task> GetVhostsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string json = await k8s.GetJsonAsync( + "vhosts.rabbitmq.com", cluster.Namespace, cluster.KubernetesCluster.Kubeconfig!, + $"rabbitmq.com/cluster={cluster.Name}", ct); + return ParseTopologyItems(json, item => + { + string k8sName = GetSpecField(item, "metadata", "name") ?? ""; + string vhostName = GetSpecField(item, "spec", "name") ?? k8sName; + return new RabbitMQVhostInfo { K8sName = k8sName, VhostName = vhostName }; + }); + } + + public async Task CreateVhostAsync( + Guid tenantId, Guid clusterId, string vhostName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "vh", vhostName); + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: Vhost + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec: + name: {vhostName} + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task DeleteVhostAsync( + Guid tenantId, Guid clusterId, string k8sName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + await k8s.DeleteManifestAsync("vhost", k8sName, cluster.Namespace, + cluster.KubernetesCluster.Kubeconfig!, ct); + } + + // ── Topology — Queues ───────────────────────────────────────────────────── + + public async Task> GetQueuesAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string json = await k8s.GetJsonAsync( + "queues.rabbitmq.com", cluster.Namespace, cluster.KubernetesCluster.Kubeconfig!, + $"rabbitmq.com/cluster={cluster.Name}", ct); + return ParseTopologyItems(json, item => new RabbitMQQueueInfo + { + K8sName = GetSpecField(item, "metadata", "name") ?? "", + QueueName = GetSpecField(item, "spec", "name") ?? "", + Vhost = GetSpecField(item, "spec", "vhost") ?? "/", + QueueType = GetSpecField(item, "spec", "type") ?? "quorum", + Durable = GetSpecBool(item, "spec", "durable") ?? true, + AutoDelete = GetSpecBool(item, "spec", "autoDelete") ?? false + }); + } + + public async Task CreateQueueAsync( + Guid tenantId, Guid clusterId, string vhost, string name, + string type, bool durable, bool autoDelete, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "q", name); + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: Queue + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec: + name: {name} + vhost: {vhost} + type: {type} + durable: {durable.ToString().ToLower()} + autoDelete: {autoDelete.ToString().ToLower()} + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task DeleteQueueAsync( + Guid tenantId, Guid clusterId, string k8sName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + await k8s.DeleteManifestAsync("queue", k8sName, cluster.Namespace, + cluster.KubernetesCluster.Kubeconfig!, ct); + } + + // ── Topology — Exchanges ────────────────────────────────────────────────── + + public async Task> GetExchangesAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string json = await k8s.GetJsonAsync( + "exchanges.rabbitmq.com", cluster.Namespace, cluster.KubernetesCluster.Kubeconfig!, + $"rabbitmq.com/cluster={cluster.Name}", ct); + return ParseTopologyItems(json, item => new RabbitMQExchangeInfo + { + K8sName = GetSpecField(item, "metadata", "name") ?? "", + ExchangeName = GetSpecField(item, "spec", "name") ?? "", + Vhost = GetSpecField(item, "spec", "vhost") ?? "/", + ExchangeType = GetSpecField(item, "spec", "type") ?? "direct", + Durable = GetSpecBool(item, "spec", "durable") ?? true, + AutoDelete = GetSpecBool(item, "spec", "autoDelete") ?? false + }); + } + + public async Task CreateExchangeAsync( + Guid tenantId, Guid clusterId, string vhost, string name, + string type, bool durable, bool autoDelete, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "ex", name); + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: Exchange + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec: + name: {name} + vhost: {vhost} + type: {type} + durable: {durable.ToString().ToLower()} + autoDelete: {autoDelete.ToString().ToLower()} + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task DeleteExchangeAsync( + Guid tenantId, Guid clusterId, string k8sName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + await k8s.DeleteManifestAsync("exchange", k8sName, cluster.Namespace, + cluster.KubernetesCluster.Kubeconfig!, ct); + } + + // ── Topology — Routing bindings (exchange→queue/exchange) ───────────────── + + public async Task> GetRoutingBindingsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string json = await k8s.GetJsonAsync( + "bindings.rabbitmq.com", cluster.Namespace, cluster.KubernetesCluster.Kubeconfig!, + $"rabbitmq.com/cluster={cluster.Name}", ct); + return ParseTopologyItems(json, item => new RabbitMQRoutingBindingInfo + { + K8sName = GetSpecField(item, "metadata", "name") ?? "", + Source = GetSpecField(item, "spec", "source") ?? "", + Destination = GetSpecField(item, "spec", "destination") ?? "", + DestinationType = GetSpecField(item, "spec", "destinationType") ?? "queue", + Vhost = GetSpecField(item, "spec", "vhost") ?? "/", + RoutingKey = GetSpecField(item, "spec", "routingKey") ?? "" + }); + } + + public async Task CreateRoutingBindingAsync( + Guid tenantId, Guid clusterId, + string vhost, string source, string destination, string destType, string routingKey, + CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "b", $"{source}-{destination}"); + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: Binding + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec: + source: {source} + destination: {destination} + destinationType: {destType} + vhost: {vhost} + routingKey: {routingKey} + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task DeleteRoutingBindingAsync( + Guid tenantId, Guid clusterId, string k8sName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + await k8s.DeleteManifestAsync("binding", k8sName, cluster.Namespace, + cluster.KubernetesCluster.Kubeconfig!, ct); + } + + // ── Topology — Users ────────────────────────────────────────────────────── + + public async Task> GetUsersAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + + string json = await k8s.GetJsonAsync( + "users.rabbitmq.com", cluster.Namespace, kubeconfig, + $"rabbitmq.com/cluster={cluster.Name}", ct); + + List users = ParseTopologyItems(json, item => + { + List tags = []; + if (item.TryGetProperty("spec", out JsonElement spec) + && spec.TryGetProperty("tags", out JsonElement tagEl) + && tagEl.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement t in tagEl.EnumerateArray()) + if (t.GetString() is string s) tags.Add(s); + } + string k8sName = GetSpecField(item, "metadata", "name") ?? ""; + return new RabbitMQUserInfo + { + K8sName = k8sName, + // Spec no longer has a name field in v1beta1; read from credentials secret below. + Username = k8sName, + Tags = tags + }; + }); + + // Enrich each user entry with the actual RabbitMQ username from the credentials secret. + // Best-effort: leave the K8s object name as fallback if the secret doesn't exist yet. + foreach (RabbitMQUserInfo u in users) + { + try + { + string? actualUsername = await k8s.GetSecretValueAsync( + $"{u.K8sName}-credentials", "username", cluster.Namespace, kubeconfig, ct); + if (actualUsername is not null) + u.Username = actualUsername; + } + catch { } + } + + return users; + } + + public async Task CreateUserAsync( + Guid tenantId, Guid clusterId, string username, string password, + IEnumerable tags, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "u", username); + string credSecretName = $"{k8sName}-credentials"; + + // Create the credential secret first so the User CRD can reference it. + string credSecret = $""" + apiVersion: v1 + kind: Secret + metadata: + name: {credSecretName} + namespace: {cluster.Namespace} + type: Opaque + data: + username: {B64(username)} + password: {B64(password)} + """; + await k8s.ApplyManifestAsync(credSecret, cluster.KubernetesCluster.Kubeconfig!, ct); + + string tagsYaml = tags.Any() + ? "\n tags:\n" + string.Join("\n", tags.Select(t => $" - {t}")) + : "\n tags: []"; + + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: User + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec:{tagsYaml} + importCredentialsSecret: + name: {credSecretName} + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + public async Task DeleteUserAsync( + Guid tenantId, Guid clusterId, string k8sName, CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + await k8s.DeleteManifestAsync("user", k8sName, cluster.Namespace, + cluster.KubernetesCluster.Kubeconfig!, ct); + // Best-effort cleanup of the credentials secret. + try + { + await k8s.DeleteManifestAsync("secret", $"{k8sName}-credentials", + cluster.Namespace, cluster.KubernetesCluster.Kubeconfig!, ct); + } + catch { } + } + + // ── Topology — Permissions ──────────────────────────────────────────────── + + public async Task SetPermissionAsync( + Guid tenantId, Guid clusterId, + string vhost, string username, string configure, string write, string read, + CancellationToken ct = default) + { + RabbitMQCluster cluster = await LoadClusterAsync(tenantId, clusterId, ct); + string k8sName = ToK8sName(cluster.Name, "perm", $"{username}-{vhost}"); + string manifest = $""" + apiVersion: rabbitmq.com/v1beta1 + kind: Permission + metadata: + name: {k8sName} + namespace: {cluster.Namespace} + labels: + rabbitmq.com/cluster: {cluster.Name} + spec: + vhost: {vhost} + user: {username} + permissions: + configure: "{configure}" + write: "{write}" + read: "{read}" + rabbitmqClusterReference: + name: {cluster.Name} + namespace: {cluster.Namespace} + """; + await k8s.ApplyManifestAsync(manifest, cluster.KubernetesCluster.Kubeconfig!, ct); + } + + // ── App bindings (MessagingBinding) ─────────────────────────────────────── + + public async Task> GetMessagingBindingsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.MessagingBindings + .Include(b => b.AppDeployment).ThenInclude(d => d.App).ThenInclude(a => a.Customer) + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .Where(b => b.TenantId == tenantId && b.RabbitMQClusterId == clusterId) + .OrderBy(b => b.AppDeployment.Name) + .ToListAsync(ct); + } + + public async Task CreateMessagingBindingAsync( + Guid tenantId, Guid clusterId, Guid appDeploymentId, + string vhost, string? queueName, string? exchangeName, + string k8sSecretName, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MessagingBinding binding = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + RabbitMQClusterId = clusterId, + AppDeploymentId = appDeploymentId, + Vhost = vhost, + QueueName = queueName, + ExchangeName = exchangeName, + KubernetesSecretName = k8sSecretName, + SyncEnabled = true + }; + + db.MessagingBindings.Add(binding); + await db.SaveChangesAsync(ct); + + // Sync immediately. + await SyncMessagingBindingAsync(tenantId, binding.Id, ct); + + return binding; + } + + public async Task DeleteMessagingBindingAsync( + Guid tenantId, Guid bindingId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MessagingBinding binding = await db.MessagingBindings + .Include(b => b.Cluster).ThenInclude(c => c.KubernetesCluster) + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .FirstOrDefaultAsync(b => b.Id == bindingId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Messaging binding not found."); + + // Best-effort: remove the K8s secret from the app's namespace. + try + { + string kubeconfig = binding.AppDeployment.Cluster.Kubeconfig!; + await k8s.DeleteManifestAsync( + "secret", binding.KubernetesSecretName, + binding.AppDeployment.Namespace, kubeconfig, ct); + } + catch { } + + // Best-effort: delete the dedicated RabbitMQ user from the cluster. + try + { + string appUsername = BindingUsername(bindingId); + string k8sUserName = ToK8sName(binding.Cluster.Name, "u", appUsername); + string rmqKubeconfig = binding.Cluster.KubernetesCluster.Kubeconfig!; + await k8s.DeleteManifestAsync("user", k8sUserName, binding.Cluster.Namespace, rmqKubeconfig, ct); + await k8s.DeleteManifestAsync("secret", $"{k8sUserName}-credentials", + binding.Cluster.Namespace, rmqKubeconfig, ct); + } + catch { } + + // Best-effort: remove the vault password for this binding. + try + { + await vaultService.DeleteRabbitMQClusterSecretAsync( + tenantId, binding.RabbitMQClusterId, BindingPasswordVaultKey(bindingId), ct); + } + catch { } + + db.MessagingBindings.Remove(binding); + await db.SaveChangesAsync(ct); + } + + public async Task SyncMessagingBindingAsync( + Guid tenantId, Guid bindingId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + MessagingBinding binding = await db.MessagingBindings + .Include(b => b.Cluster).ThenInclude(c => c.KubernetesCluster) + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .FirstOrDefaultAsync(b => b.Id == bindingId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Messaging binding not found."); + + // Use a dedicated per-binding RabbitMQ user instead of admin credentials. + string appUsername = BindingUsername(bindingId); + string passwordKey = BindingPasswordVaultKey(bindingId); + + string? appPassword = await vaultService.GetRabbitMQClusterSecretValueAsync( + tenantId, binding.RabbitMQClusterId, passwordKey, ct); + + if (appPassword is null) + { + // First sync: provision the dedicated user on the RabbitMQ cluster. + appPassword = GeneratePassword(); + await CreateUserAsync(tenantId, binding.RabbitMQClusterId, appUsername, appPassword, [], ct); + await SetPermissionAsync(tenantId, binding.RabbitMQClusterId, + binding.Vhost, appUsername, ".*", ".*", ".*", ct); + await vaultService.SetRabbitMQClusterSecretAsync( + tenantId, binding.RabbitMQClusterId, passwordKey, appPassword, ct); + } + + string host = $"{binding.Cluster.Name}-svc.{binding.Cluster.Namespace}.svc.cluster.local"; + string encodedVhost = Uri.EscapeDataString(binding.Vhost == "/" ? "/" : binding.Vhost.TrimStart('/')); + string amqpUrl = $"amqp://{appUsername}:{appPassword}@{host}:5672/{encodedVhost}"; + + StringBuilder secretData = new(); + secretData.AppendLine($" RABBITMQ_HOST: {B64(host)}"); + secretData.AppendLine($" RABBITMQ_PORT: {B64("5672")}"); + secretData.AppendLine($" RABBITMQ_VHOST: {B64(binding.Vhost)}"); + secretData.AppendLine($" RABBITMQ_USERNAME: {B64(appUsername)}"); + secretData.AppendLine($" RABBITMQ_PASSWORD: {B64(appPassword)}"); + secretData.AppendLine($" RABBITMQ_URL: {B64(amqpUrl)}"); + + if (!string.IsNullOrEmpty(binding.QueueName)) + secretData.AppendLine($" RABBITMQ_QUEUE: {B64(binding.QueueName)}"); + if (!string.IsNullOrEmpty(binding.ExchangeName)) + secretData.AppendLine($" RABBITMQ_EXCHANGE: {B64(binding.ExchangeName)}"); + + string appKubeconfig = binding.AppDeployment.Cluster.Kubeconfig!; + await k8s.EnsureNamespaceAsync(binding.AppDeployment.Namespace, appKubeconfig, ct); + + string secretManifest = $""" + apiVersion: v1 + kind: Secret + metadata: + name: {binding.KubernetesSecretName} + namespace: {binding.AppDeployment.Namespace} + type: Opaque + data: + {secretData} + """; + + await k8s.ApplyManifestAsync(secretManifest, appKubeconfig, ct); + + binding.LastSyncedAt = DateTime.UtcNow; + using ApplicationDbContext db2 = dbFactory.CreateDbContext(); + db2.MessagingBindings.Update(binding); + await db2.SaveChangesAsync(ct); + } + + // ── Manifest building ───────────────────────────────────────────────────── + + private static string BuildClusterManifest(RabbitMQCluster cluster) + { + StringBuilder sb = new(); + sb.AppendLine("apiVersion: rabbitmq.com/v1beta1"); + sb.AppendLine("kind: RabbitmqCluster"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {cluster.Name}"); + sb.AppendLine($" namespace: {cluster.Namespace}"); + sb.AppendLine("spec:"); + sb.AppendLine($" replicas: {cluster.Replicas}"); + sb.AppendLine($" image: rabbitmq:{cluster.RabbitMQVersion}-management"); + sb.AppendLine(" persistence:"); + sb.AppendLine($" storage: {cluster.StorageSize}"); + + if (!string.IsNullOrEmpty(cluster.StorageClass)) + sb.AppendLine($" storageClassName: {cluster.StorageClass}"); + + sb.AppendLine(" resources:"); + sb.AppendLine(" requests:"); + sb.AppendLine(" cpu: 200m"); + sb.AppendLine(" memory: 512Mi"); + sb.AppendLine(" limits:"); + sb.AppendLine(" cpu: '2'"); + sb.AppendLine(" memory: 2Gi"); + + // Enable the management plugin (included in the -management image tag). + sb.AppendLine(" rabbitmq:"); + sb.AppendLine(" additionalPlugins:"); + sb.AppendLine(" - rabbitmq_management"); + sb.AppendLine(" - rabbitmq_peer_discovery_k8s"); + + return sb.ToString(); + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private static RabbitMQClusterInfo? ParseClusterStatus(string json, string name, string ns) + { + try + { + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + + bool allReady = false; + bool available = false; + + if (root.TryGetProperty("status", out JsonElement status) + && status.TryGetProperty("conditions", out JsonElement conditions)) + { + foreach (JsonElement condition in conditions.EnumerateArray()) + { + string? type = condition.TryGetProperty("type", out JsonElement t) ? t.GetString() : null; + string? condStatus = condition.TryGetProperty("status", out JsonElement s) ? s.GetString() : null; + + if (type == "AllReplicasReady" && condStatus == "True") allReady = true; + if (type == "ClusterAvailable" && condStatus == "True") available = true; + } + } + + return new RabbitMQClusterInfo + { + Name = name, + Namespace = ns, + AllReplicasReady = allReady, + ClusterAvailable = available + }; + } + catch + { + return null; + } + } + + private async Task> GetTopologyCrdNamesAsync( + string crdResource, string ns, string kubeconfig, string labelSelector, CancellationToken ct) + { + string json = await k8s.GetJsonAsync(crdResource, ns, kubeconfig, labelSelector, ct); + using JsonDocument doc = JsonDocument.Parse(json); + + List names = []; + + if (doc.RootElement.TryGetProperty("items", out JsonElement items)) + { + foreach (JsonElement item in items.EnumerateArray()) + { + if (item.TryGetProperty("metadata", out JsonElement meta) + && meta.TryGetProperty("name", out JsonElement nameEl)) + { + string? name = nameEl.GetString(); + if (name is not null) names.Add(name); + } + } + } + + return names; + } + + private async Task PruneBackupsAsync( + Guid tenantId, Guid clusterId, int maxBackups, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List backups = await db.RabbitMQBackups + .Include(b => b.StorageLink) + .Where(b => b.TenantId == tenantId && b.RabbitMQClusterId == clusterId + && b.Status == RabbitMQBackupStatus.Ready) + .OrderByDescending(b => b.CreatedAt) + .ToListAsync(ct); + + List toDelete = backups.Skip(maxBackups).ToList(); + + foreach (RabbitMQBackup old in toDelete) + { + if (old.StorageLink is not null) + { + try { await DeleteFromS3Async(tenantId, old.StorageLink, old.ObjectKey, ct); } + catch { } + } + + db.RabbitMQBackups.Remove(old); + } + + if (toDelete.Count > 0) + await db.SaveChangesAsync(ct); + } + + // ── App deployment listing ──────────────────────────────────────────────── + + public async Task> GetTenantDeploymentsAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AppDeployments + .Include(d => d.App).ThenInclude(a => a.Customer) + .Include(d => d.Cluster) + .Where(d => d.App.Customer.TenantId == tenantId) + .OrderBy(d => d.App.Customer.Name).ThenBy(d => d.App.Name).ThenBy(d => d.Name) + .ToListAsync(ct); + } + + // ── Topology helpers ────────────────────────────────────────────────────── + + private async Task LoadClusterAsync( + Guid tenantId, Guid clusterId, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.RabbitMQClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("RabbitMQ cluster not found."); + } + + private static string ToK8sName(string clusterName, string prefix, string resourceName) + { + string sanitized = Regex.Replace(resourceName.ToLowerInvariant(), @"[^a-z0-9]+", "-") + .Trim('-'); + if (sanitized.Length > 40) sanitized = sanitized[..40].TrimEnd('-'); + string name = $"{clusterName}-{prefix}-{sanitized}"; + if (name.Length > 63) name = name[..63].TrimEnd('-'); + return name; + } + + private static string B64(string value) => + Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); + + private static string GeneratePassword() + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return string.Create(24, chars, static (span, c) => + { + Span bytes = stackalloc byte[24]; + System.Security.Cryptography.RandomNumberGenerator.Fill(bytes); + for (int i = 0; i < span.Length; i++) + span[i] = c[bytes[i] % c.Length]; + }); + } + + private static string BindingUsername(Guid bindingId) => $"mb-{bindingId:N}"; + + private static string BindingPasswordVaultKey(Guid bindingId) => $"MB_{bindingId:N}_PASSWORD"; + + private static List ParseTopologyItems(string json, Func selector) + { + List results = []; + try + { + using JsonDocument doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("items", out JsonElement items)) + foreach (JsonElement item in items.EnumerateArray()) + results.Add(selector(item)); + } + catch { } + return results; + } + + private static string? GetSpecField(JsonElement item, string section, string field) + { + if (item.TryGetProperty(section, out JsonElement sec) + && sec.TryGetProperty(field, out JsonElement f)) + return f.ValueKind == JsonValueKind.Null ? null : f.GetString(); + return null; + } + + private static bool? GetSpecBool(JsonElement item, string section, string field) + { + if (item.TryGetProperty(section, out JsonElement sec) + && sec.TryGetProperty(field, out JsonElement f) + && (f.ValueKind == JsonValueKind.True || f.ValueKind == JsonValueKind.False)) + return f.GetBoolean(); + return null; + } + + // ── S3 helpers ──────────────────────────────────────────────────────────── + + private async Task UploadToS3Async( + Guid tenantId, StorageLink link, string key, byte[] data, CancellationToken ct) + { + using AmazonS3Client s3 = await CreateS3ClientAsync(tenantId, link, ct); + + await s3.PutObjectAsync(new PutObjectRequest + { + BucketName = link.BucketName, + Key = key, + InputStream = new MemoryStream(data), + ContentType = "application/json" + }, ct); + } + + private async Task DownloadFromS3Async( + Guid tenantId, StorageLink link, string key, CancellationToken ct) + { + using AmazonS3Client s3 = await CreateS3ClientAsync(tenantId, link, ct); + + GetObjectResponse obj = await s3.GetObjectAsync(new GetObjectRequest + { + BucketName = link.BucketName, + Key = key + }, ct); + + using MemoryStream ms = new(); + await obj.ResponseStream.CopyToAsync(ms, ct); + return ms.ToArray(); + } + + private async Task DeleteFromS3Async( + Guid tenantId, StorageLink link, string key, CancellationToken ct) + { + using AmazonS3Client s3 = await CreateS3ClientAsync(tenantId, link, ct); + + await s3.DeleteObjectAsync(new DeleteObjectRequest + { + BucketName = link.BucketName, + Key = key + }, ct); + } + + private async Task CreateS3ClientAsync( + Guid tenantId, StorageLink link, CancellationToken ct) + { + string? accessKey = await vaultService.GetStorageLinkSecretValueAsync( + tenantId, link.Id, "ACCESS_KEY", ct); + string? secretKey = await vaultService.GetStorageLinkSecretValueAsync( + tenantId, link.Id, "SECRET_KEY", ct); + + return new AmazonS3Client(accessKey, secretKey, new AmazonS3Config + { + ServiceURL = link.Endpoint, + ForcePathStyle = true + }); + } +} diff --git a/src/EntKube.Web/Services/RedisService.cs b/src/EntKube.Web/Services/RedisService.cs new file mode 100644 index 0000000..fe53f4e --- /dev/null +++ b/src/EntKube.Web/Services/RedisService.cs @@ -0,0 +1,582 @@ +using System.Security.Cryptography; +using System.Text; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +// ── DTOs ───────────────────────────────────────────────────────────────────── + +public class RedisOperatorStatus +{ + public bool OperatorAvailable { get; set; } + public string? OperatorClusterName { get; set; } +} + +// ── Service ─────────────────────────────────────────────────────────────────── + +/// +/// Manages Redis clusters via the OT-Container-Kit Redis Operator. +/// +/// Applies RedisCluster CRDs (redis.redis.opstreelabs.in/v1beta2). Before +/// creating the CRD, EntKube generates an auth password, stores it in the +/// tenant vault, and creates a Kubernetes Secret — because the operator reads +/// credentials from a pre-existing Secret rather than generating them itself. +/// +/// Connection details (host, port, password) are surfaced in the Cache tab's +/// credentials panel so engineers can wire up applications. +/// +public class RedisService( + IDbContextFactory dbFactory, + IKubernetesClientFactory k8s, + VaultService vaultService) +{ + private const int RedisPort = 6379; + + // ── Queries ─────────────────────────────────────────────────────────────── + + public async Task> GetClustersAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RedisClusters + .Include(c => c.KubernetesCluster).ThenInclude(k => k.Environment) + .Where(c => c.TenantId == tenantId) + .OrderBy(c => c.Name) + .ToListAsync(ct); + } + + // ── Operator detection ──────────────────────────────────────────────────── + + public async Task GetOperatorStatusAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent? op = await db.ClusterComponents + .Include(c => c.Cluster) + .FirstOrDefaultAsync(c => c.Cluster.TenantId == tenantId + && c.Status == ComponentStatus.Installed + && (c.Name == "redis-operator" + || c.ReleaseName == "redis-operator"), ct); + + return new RedisOperatorStatus + { + OperatorAvailable = op is not null, + OperatorClusterName = op?.Cluster.Name + }; + } + + // ── Cluster lifecycle ───────────────────────────────────────────────────── + + public async Task CreateClusterAsync( + Guid tenantId, + Guid kubernetesClusterId, + string name, + string ns, + int clusterSize, + string redisVersion, + string storageSize, + string? storageClass, + bool persistenceEnabled, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + if (await db.RedisClusters.AnyAsync( + c => c.KubernetesClusterId == kubernetesClusterId + && c.Name == name + && c.Namespace == ns, ct)) + throw new InvalidOperationException( + $"A Redis cluster named '{name}' already exists in namespace '{ns}'."); + + RedisCluster cluster = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KubernetesClusterId = kubernetesClusterId, + Name = name, + Namespace = ns, + ClusterSize = clusterSize, + RedisVersion = redisVersion, + StorageSize = storageSize, + StorageClass = storageClass, + PersistenceEnabled = persistenceEnabled, + Status = RedisClusterStatus.Creating + }; + + db.RedisClusters.Add(cluster); + await db.SaveChangesAsync(ct); + + try + { + KubernetesCluster k8sCluster = await db.KubernetesClusters + .FirstAsync(c => c.Id == kubernetesClusterId, ct); + string kubeconfig = k8sCluster.Kubeconfig!; + + // Generate a strong auth password and persist it in the vault. + string password = GeneratePassword(); + await vaultService.SetRedisClusterSecretAsync( + tenantId, cluster.Id, "REDIS_PASSWORD", password, ct); + await vaultService.SetRedisClusterSecretAsync( + tenantId, cluster.Id, "REDIS_HOST", + $"{name}-leader.{ns}.svc.cluster.local", ct); + await vaultService.SetRedisClusterSecretAsync( + tenantId, cluster.Id, "REDIS_PORT", RedisPort.ToString(), ct); + + await k8s.EnsureNamespaceAsync(ns, kubeconfig, ct); + + // Create the auth Secret the operator reads at startup. + string authSecretManifest = BuildAuthSecretManifest(name, ns, password); + await k8s.ApplyManifestAsync(authSecretManifest, kubeconfig, ct); + + // Apply the RedisCluster CRD. + string clusterManifest = BuildClusterManifest(cluster); + await k8s.ApplyManifestAsync(clusterManifest, kubeconfig, ct); + + // Leave status as Creating — GetClusterDetailAsync reconciles to Running + // once the operator reports all pods ready. + } + catch (Exception ex) + { + cluster.Status = RedisClusterStatus.Failed; + cluster.LastError = ex.Message; + } + + using ApplicationDbContext db2 = dbFactory.CreateDbContext(); + db2.RedisClusters.Update(cluster); + await db2.SaveChangesAsync(ct); + + return cluster; + } + + public async Task DeleteClusterAsync(Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RedisCluster cluster = await db.RedisClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Redis cluster not found."); + + cluster.Status = RedisClusterStatus.Deleting; + await db.SaveChangesAsync(ct); + + try + { + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + await k8s.DeleteManifestAsync("rediscluster", cluster.Name, cluster.Namespace, kubeconfig, ct); + } + catch (Exception ex) + { + cluster.Status = RedisClusterStatus.Failed; + cluster.LastError = ex.Message; + await db.SaveChangesAsync(ct); + throw; + } + + db.RedisClusters.Remove(cluster); + await db.SaveChangesAsync(ct); + } + + // ── Credentials ─────────────────────────────────────────────────────────── + + public async Task<(string? password, string? host, string? port)> GetCredentialsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + string? password = await vaultService.GetRedisClusterSecretValueAsync( + tenantId, clusterId, "REDIS_PASSWORD", ct); + string? host = await vaultService.GetRedisClusterSecretValueAsync( + tenantId, clusterId, "REDIS_HOST", ct); + string? port = await vaultService.GetRedisClusterSecretValueAsync( + tenantId, clusterId, "REDIS_PORT", ct); + + return (password, host, port); + } + + // ── App bindings (CacheBinding) ─────────────────────────────────────────── + + public async Task> GetTenantDeploymentsAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.AppDeployments + .Include(d => d.App).ThenInclude(a => a.Customer) + .Include(d => d.Cluster) + .Where(d => d.App.Customer.TenantId == tenantId) + .OrderBy(d => d.App.Name).ThenBy(d => d.Name) + .ToListAsync(ct); + } + + public async Task> GetCacheBindingsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.CacheBindings + .Include(b => b.AppDeployment).ThenInclude(d => d.App).ThenInclude(a => a.Customer) + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .Where(b => b.TenantId == tenantId && b.RedisClusterId == clusterId) + .OrderBy(b => b.AppDeployment.Name) + .ToListAsync(ct); + } + + public async Task> GetCacheBindingsForDeploymentAsync( + Guid appDeploymentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.CacheBindings + .Include(b => b.RedisCluster).ThenInclude(c => c.KubernetesCluster) + .Where(b => b.AppDeploymentId == appDeploymentId) + .OrderBy(b => b.RedisCluster.Name) + .ToListAsync(ct); + } + + public async Task CreateCacheBindingAsync( + Guid tenantId, Guid clusterId, Guid appDeploymentId, + string k8sSecretName, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CacheBinding binding = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + RedisClusterId = clusterId, + AppDeploymentId = appDeploymentId, + KubernetesSecretName = k8sSecretName, + SyncEnabled = true + }; + + db.CacheBindings.Add(binding); + await db.SaveChangesAsync(ct); + + await SyncCacheBindingAsync(tenantId, binding.Id, ct); + + return binding; + } + + public async Task DeleteCacheBindingAsync( + Guid tenantId, Guid bindingId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CacheBinding binding = await db.CacheBindings + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .FirstOrDefaultAsync(b => b.Id == bindingId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Cache binding not found."); + + try + { + await k8s.DeleteManifestAsync( + "secret", binding.KubernetesSecretName, + binding.AppDeployment.Namespace, binding.AppDeployment.Cluster.Kubeconfig!, ct); + } + catch { } + + db.CacheBindings.Remove(binding); + await db.SaveChangesAsync(ct); + } + + public async Task SyncCacheBindingAsync( + Guid tenantId, Guid bindingId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CacheBinding binding = await db.CacheBindings + .Include(b => b.RedisCluster).ThenInclude(c => c.KubernetesCluster) + .Include(b => b.AppDeployment).ThenInclude(d => d.Cluster) + .FirstOrDefaultAsync(b => b.Id == bindingId && b.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Cache binding not found."); + + (string? password, string? host, string? port) = + await GetCredentialsAsync(tenantId, binding.RedisClusterId, ct); + + if (password is null || host is null || port is null) + throw new InvalidOperationException("Redis cluster credentials not found in vault."); + + string redisUrl = $"redis://:{password}@{host}:{port}"; + + string appKubeconfig = binding.AppDeployment.Cluster.Kubeconfig!; + await k8s.EnsureNamespaceAsync(binding.AppDeployment.Namespace, appKubeconfig, ct); + + string secretManifest = $""" + apiVersion: v1 + kind: Secret + metadata: + name: {binding.KubernetesSecretName} + namespace: {binding.AppDeployment.Namespace} + type: Opaque + data: + REDIS_HOST: {B64(host)} + REDIS_PORT: {B64(port)} + REDIS_PASSWORD: {B64(password)} + REDIS_URL: {B64(redisUrl)} + """; + + await k8s.ApplyManifestAsync(secretManifest, appKubeconfig, ct); + + binding.LastSyncedAt = DateTime.UtcNow; + using ApplicationDbContext db2 = dbFactory.CreateDbContext(); + db2.CacheBindings.Update(binding); + await db2.SaveChangesAsync(ct); + } + + private static string B64(string value) => + Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); + + // ── Live detail ─────────────────────────────────────────────────────────── + + public async Task GetClusterDetailAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RedisCluster? cluster = await db.RedisClusters + .Include(c => c.KubernetesCluster) + .FirstOrDefaultAsync(c => c.Id == clusterId && c.TenantId == tenantId, ct); + + if (cluster is null) + return null; + + RedisClusterDetail detail = new() { Cluster = cluster, Phase = "Querying..." }; + + try + { + string kubeconfig = cluster.KubernetesCluster.Kubeconfig!; + + string crdJson = await k8s.GetJsonAsync( + $"rediscluster.redis.redis.opstreelabs.in/{cluster.Name}", + cluster.Namespace, kubeconfig, ct: ct); + + ParseCrdStatus(crdJson, detail); + + string leaderJson = await k8s.GetJsonAsync( + "pods", cluster.Namespace, kubeconfig, + $"app={cluster.Name}-leader", ct); + + string followerJson = await k8s.GetJsonAsync( + "pods", cluster.Namespace, kubeconfig, + $"app={cluster.Name}-follower", ct); + + detail.Pods = + [ + .. ParsePodList(leaderJson, "leader"), + .. ParsePodList(followerJson, "follower"), + ]; + + await ReconcileStatusAsync(cluster, detail, ct); + } + catch + { + detail.Phase = "Unable to reach cluster"; + } + + return detail; + } + + private static void ParseCrdStatus(string json, RedisClusterDetail detail) + { + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(json); + System.Text.Json.JsonElement root = doc.RootElement; + + if (!root.TryGetProperty("status", out System.Text.Json.JsonElement status)) + { + detail.Phase = "Initialising"; + return; + } + + if (status.TryGetProperty("readyLeaderReplicas", out System.Text.Json.JsonElement rl)) + detail.ReadyLeaders = rl.GetInt32(); + + if (status.TryGetProperty("readyFollowerReplicas", out System.Text.Json.JsonElement rf)) + detail.ReadyFollowers = rf.GetInt32(); + + detail.Phase = (detail.ReadyLeaders >= detail.Cluster.ClusterSize + && detail.ReadyFollowers >= detail.Cluster.ClusterSize) + ? "Cluster in healthy state" + : "Initialising"; + } + + private static List ParsePodList(string json, string role) + { + List pods = []; + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(json); + System.Text.Json.JsonElement root = doc.RootElement; + + if (!root.TryGetProperty("items", out System.Text.Json.JsonElement items)) + return pods; + + foreach (System.Text.Json.JsonElement item in items.EnumerateArray()) + { + string name = item.GetProperty("metadata").GetProperty("name").GetString() ?? ""; + string podStatus = "Unknown"; + bool ready = false; + string? node = null; + DateTime? startTime = null; + int restarts = 0; + + if (item.TryGetProperty("spec", out System.Text.Json.JsonElement spec) + && spec.TryGetProperty("nodeName", out System.Text.Json.JsonElement nodeName)) + node = nodeName.GetString(); + + if (item.TryGetProperty("status", out System.Text.Json.JsonElement statusEl)) + { + if (statusEl.TryGetProperty("phase", out System.Text.Json.JsonElement phaseEl)) + podStatus = phaseEl.GetString() ?? "Unknown"; + + if (statusEl.TryGetProperty("startTime", out System.Text.Json.JsonElement startEl) + && DateTime.TryParse(startEl.GetString(), null, + System.Globalization.DateTimeStyles.RoundtripKind, out DateTime parsed)) + startTime = parsed; + + if (statusEl.TryGetProperty("containerStatuses", out System.Text.Json.JsonElement containers)) + { + foreach (System.Text.Json.JsonElement container in containers.EnumerateArray()) + { + if (container.TryGetProperty("restartCount", out System.Text.Json.JsonElement rc)) + restarts += rc.GetInt32(); + + if (container.TryGetProperty("ready", out System.Text.Json.JsonElement readyEl)) + ready = ready || readyEl.GetBoolean(); + } + } + } + + pods.Add(new RedisPodInfo + { + Name = name, + Role = role, + Status = podStatus, + Ready = ready, + Node = node, + StartTime = startTime, + Restarts = restarts + }); + } + + return [.. pods.OrderBy(p => p.Name)]; + } + + private async Task ReconcileStatusAsync( + RedisCluster cluster, RedisClusterDetail detail, CancellationToken ct) + { + bool healthy = detail.ReadyLeaders >= cluster.ClusterSize + && detail.ReadyFollowers >= cluster.ClusterSize; + + RedisClusterStatus newStatus = healthy + ? RedisClusterStatus.Running + : cluster.Status == RedisClusterStatus.Running + ? RedisClusterStatus.Running // don't regress a running cluster on transient blip + : RedisClusterStatus.Creating; + + if (newStatus == cluster.Status) + return; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + RedisCluster? tracked = await db.RedisClusters.FindAsync([cluster.Id], ct); + if (tracked is not null && tracked.Status != newStatus) + { + tracked.Status = newStatus; + cluster.Status = newStatus; + await db.SaveChangesAsync(ct); + } + } + + // ── Manifest builders ───────────────────────────────────────────────────── + + private static string BuildAuthSecretManifest(string clusterName, string ns, string password) + { + string b64Password = Convert.ToBase64String(Encoding.UTF8.GetBytes(password)); + + return $""" + apiVersion: v1 + kind: Secret + metadata: + name: {clusterName}-auth + namespace: {ns} + type: Opaque + data: + password: {b64Password} + """; + } + + private static string BuildClusterManifest(RedisCluster cluster) + { + StringBuilder sb = new(); + sb.AppendLine("apiVersion: redis.redis.opstreelabs.in/v1beta2"); + sb.AppendLine("kind: RedisCluster"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {cluster.Name}"); + sb.AppendLine($" namespace: {cluster.Namespace}"); + sb.AppendLine("spec:"); + sb.AppendLine($" clusterSize: {cluster.ClusterSize}"); + sb.AppendLine($" clusterVersion: {MajorVersion(cluster.RedisVersion)}"); + sb.AppendLine($" persistenceEnabled: {cluster.PersistenceEnabled.ToString().ToLower()}"); + sb.AppendLine(" kubernetesConfig:"); + sb.AppendLine($" image: quay.io/opstree/redis:{cluster.RedisVersion}"); + sb.AppendLine(" imagePullPolicy: IfNotPresent"); + sb.AppendLine(" redisSecret:"); + sb.AppendLine($" name: {cluster.Name}-auth"); + sb.AppendLine(" key: password"); + sb.AppendLine(" resources:"); + sb.AppendLine(" requests:"); + sb.AppendLine(" cpu: 100m"); + sb.AppendLine(" memory: 128Mi"); + sb.AppendLine(" limits:"); + sb.AppendLine(" cpu: '1'"); + sb.AppendLine(" memory: 1Gi"); + + if (cluster.PersistenceEnabled) + { + sb.AppendLine(" storage:"); + sb.AppendLine(" volumeClaimTemplate:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" accessModes:"); + sb.AppendLine(" - ReadWriteOnce"); + sb.AppendLine(" resources:"); + sb.AppendLine(" requests:"); + sb.AppendLine($" storage: {cluster.StorageSize}"); + + if (!string.IsNullOrEmpty(cluster.StorageClass)) + sb.AppendLine($" storageClassName: {cluster.StorageClass}"); + + sb.AppendLine(" nodeConfVolume: true"); + sb.AppendLine(" nodeConfVolumeClaimTemplate:"); + sb.AppendLine(" spec:"); + sb.AppendLine(" accessModes:"); + sb.AppendLine(" - ReadWriteOnce"); + sb.AppendLine(" resources:"); + sb.AppendLine(" requests:"); + sb.AppendLine(" storage: 1Gi"); + + if (!string.IsNullOrEmpty(cluster.StorageClass)) + sb.AppendLine($" storageClassName: {cluster.StorageClass}"); + } + + // fsGroup 1000 matches the Redis process uid so the operator-mounted + // /node-conf volume is writable and nodes.conf can be created/locked. + sb.AppendLine(" podSecurityContext:"); + sb.AppendLine(" runAsUser: 1000"); + sb.AppendLine(" fsGroup: 1000"); + sb.AppendLine(" redisExporter:"); + sb.AppendLine(" enabled: true"); + sb.AppendLine(" image: quay.io/opstree/redis-exporter:v1.44.0"); + + return sb.ToString(); + } + + // "v7.0.15" -> "v7", "v7" -> "v7" + private static string MajorVersion(string version) + { + int dot = version.IndexOf('.'); + return dot > 0 ? version[..dot] : version; + } + + private static string GeneratePassword() + { + byte[] bytes = RandomNumberGenerator.GetBytes(32); + return Convert.ToBase64String(bytes) + .Replace("+", "-").Replace("/", "_").Replace("=", "")[..43]; + } +} diff --git a/src/EntKube.Web/Services/RegisteredPostgresService.cs b/src/EntKube.Web/Services/RegisteredPostgresService.cs new file mode 100644 index 0000000..1bb34fe --- /dev/null +++ b/src/EntKube.Web/Services/RegisteredPostgresService.cs @@ -0,0 +1,941 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// Manages databases and credentials on vanilla (non-CNPG) PostgreSQL servers +/// that are registered with EntKube. SQL is executed via kubectl exec + psql +/// on the admin pod, using credentials stored in the vault. +/// +public class RegisteredPostgresService( + IDbContextFactory dbFactory, + VaultService vaultService, + IKubernetesClientFactory k8sFactory, + StorageBrowserService storageBrowserService) +{ + // ──────── Instance Queries ──────── + + /// + /// Returns all registered Postgres instances for a tenant, with cluster and + /// environment navigation properties loaded. + /// + public async Task> GetInstancesAsync( + Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RegisteredPostgresInstances + .Include(i => i.KubernetesCluster) + .ThenInclude(k => k.Environment) + .Include(i => i.Databases) + .Where(i => i.TenantId == tenantId) + .OrderBy(i => i.Name) + .ToListAsync(ct); + } + + // ──────── Instance Lifecycle ──────── + + /// + /// Registers a Postgres server with EntKube and stores the admin password in + /// the vault. Optionally tests the connection before persisting. + /// + public async Task RegisterInstanceAsync( + Guid tenantId, + Guid kubernetesClusterId, + string name, + string ns, + string serviceName, + int port, + string adminPodName, + string adminUsername, + string adminPassword, + string? notes = null, + bool testConnection = true, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + KubernetesCluster k8sCluster = await db.KubernetesClusters + .FirstAsync(k => k.Id == kubernetesClusterId, ct); + + if (testConnection) + { + await TestConnectionCoreAsync(adminPodName, ns, adminUsername, adminPassword, k8sCluster.Kubeconfig!, ct); + } + + RegisteredPostgresInstance instance = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + KubernetesClusterId = kubernetesClusterId, + Name = name, + Namespace = ns, + ServiceName = serviceName, + Port = port, + AdminPodName = adminPodName, + AdminUsername = adminUsername, + Notes = notes + }; + + db.RegisteredPostgresInstances.Add(instance); + await db.SaveChangesAsync(ct); + + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetRegisteredPostgresAdminPasswordAsync(tenantId, instance.Id, adminPassword, ct); + + return instance; + } + + /// + /// Tests connectivity to the Postgres pod by running a trivial query. + /// Throws InvalidOperationException on failure. + /// + public async Task TestConnectionAsync(Guid tenantId, Guid instanceId, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + await TestConnectionCoreAsync( + instance.AdminPodName, instance.Namespace, instance.AdminUsername, + adminPassword, cluster.Kubeconfig!, ct); + } + + /// + /// Lists all databases on the Postgres server (from pg_database, excluding + /// internal system databases like template0/template1). + /// + public async Task> ListServerDatabasesAsync( + Guid tenantId, Guid instanceId, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + // Run SQL that outputs one database name per line, excluding system templates. + const string sql = "SELECT datname FROM pg_database WHERE datname NOT IN ('template0','template1') ORDER BY datname;"; + + string output = await k8sFactory.ExecuteSqlOnPodWithOutputAsync( + instance.AdminPodName, instance.Namespace, sql, cluster.Kubeconfig!, + instance.AdminUsername, adminPassword, ct); + + return ParsePsqlColumnOutput(output); + } + + /// + /// Updates the instance record. Does not re-verify the connection. + /// To change the admin password call UpdateAdminPasswordAsync separately. + /// + public async Task UpdateInstanceAsync( + Guid tenantId, Guid instanceId, + string? name = null, string? notes = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + if (name is not null) instance.Name = name; + if (notes is not null) instance.Notes = notes; + + await db.SaveChangesAsync(ct); + } + + /// + /// Updates the admin password stored in the vault. Does not ALTER the role on the server. + /// + public async Task UpdateAdminPasswordAsync( + Guid tenantId, Guid instanceId, string newPassword, CancellationToken ct = default) + { + await vaultService.SetRegisteredPostgresAdminPasswordAsync(tenantId, instanceId, newPassword, ct); + } + + /// + /// Removes the instance record and all managed databases and vault secrets. + /// Does not drop databases from the Postgres server itself. + /// + public async Task DeleteInstanceAsync(Guid tenantId, Guid instanceId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .Include(i => i.Databases) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + foreach (RegisteredPostgresDatabase database in instance.Databases) + { + await DeleteDatabaseSecretsAsync(tenantId, database.Id, ct); + } + + db.RegisteredPostgresInstances.Remove(instance); + await db.SaveChangesAsync(ct); + } + + // ──────── Database Management ──────── + + /// + /// Creates a new database on the server. Generates a random password, runs + /// CREATE ROLE + CREATE DATABASE on the admin pod, then stores credentials + /// in the vault tagged for Kubernetes sync. + /// + public async Task CreateDatabaseAsync( + Guid tenantId, Guid instanceId, string databaseName, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + string owner = $"{databaseName}_owner"; + string password = GeneratePassword(); + + RegisteredPostgresDatabase database = new() + { + Id = Guid.NewGuid(), + RegisteredPostgresInstanceId = instance.Id, + Name = databaseName, + Owner = owner, + Status = RegisteredPostgresDatabaseStatus.Creating + }; + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + db.RegisteredPostgresDatabases.Add(database); + await db.SaveChangesAsync(ct); + + string sql = $""" + CREATE ROLE "{owner}" WITH LOGIN PASSWORD '{password}'; + CREATE DATABASE "{databaseName}" OWNER "{owner}"; + """; + + await k8sFactory.ExecuteSqlOnPodAsync( + instance.AdminPodName, instance.Namespace, sql, cluster.Kubeconfig!, + instance.AdminUsername, adminPassword, ct); + + database.Status = RegisteredPostgresDatabaseStatus.Ready; + await db.SaveChangesAsync(ct); + + string k8sSecretName = $"{instance.ServiceName}-{databaseName}-credentials"; + string host = $"{instance.ServiceName}.{instance.Namespace}.svc.cluster.local"; + + await StoreDatabaseSecretAsync(tenantId, database.Id, "HOST", host, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PORT", instance.Port.ToString(), k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "DATABASE", databaseName, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "USERNAME", owner, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PASSWORD", password, k8sSecretName, instance.Namespace, ct); + + return database; + } + + /// + /// Imports an existing database so EntKube can manage its credentials. + /// The caller provides the existing owner role and its current password. + /// No SQL is run on the server — only vault records are created. + /// + public async Task ImportDatabaseAsync( + Guid tenantId, Guid instanceId, + string databaseName, string ownerRole, string ownerPassword, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .Include(i => i.KubernetesCluster) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + RegisteredPostgresDatabase database = new() + { + Id = Guid.NewGuid(), + RegisteredPostgresInstanceId = instance.Id, + Name = databaseName, + Owner = ownerRole, + Status = RegisteredPostgresDatabaseStatus.Ready + }; + + db.RegisteredPostgresDatabases.Add(database); + await db.SaveChangesAsync(ct); + + string k8sSecretName = $"{instance.ServiceName}-{databaseName}-credentials"; + string host = $"{instance.ServiceName}.{instance.Namespace}.svc.cluster.local"; + + await StoreDatabaseSecretAsync(tenantId, database.Id, "HOST", host, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PORT", instance.Port.ToString(), k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "DATABASE", databaseName, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "USERNAME", ownerRole, k8sSecretName, instance.Namespace, ct); + await StoreDatabaseSecretAsync(tenantId, database.Id, "PASSWORD", ownerPassword, k8sSecretName, instance.Namespace, ct); + + return database; + } + + /// + /// Drops a database and its owner role from the server, removes vault secrets, + /// and deletes the database record. + /// + public async Task DropDatabaseAsync( + Guid tenantId, Guid instanceId, Guid databaseId, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.RegisteredPostgresInstanceId == instance.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + try + { + string sql = $""" + DROP DATABASE IF EXISTS "{database.Name}"; + DROP ROLE IF EXISTS "{database.Owner}"; + """; + + await k8sFactory.ExecuteSqlOnPodAsync( + instance.AdminPodName, instance.Namespace, sql, cluster.Kubeconfig!, + instance.AdminUsername, adminPassword, ct); + } + catch (InvalidOperationException) + { + // SQL failed (e.g. database doesn't exist on server, pod unreachable). + // Continue with local cleanup so the user isn't stuck. + } + + // Delete K8s secrets from the instance namespace and bound app namespaces. + + string primarySecretName = $"{instance.ServiceName}-{database.Name}-credentials"; + string kubeconfig = cluster.Kubeconfig!; + + try + { + await k8sFactory.DeleteManifestAsync("Secret", primarySecretName, instance.Namespace, kubeconfig, ct); + } + catch { } + + List bindings = await db.DatabaseBindings + .Include(b => b.AppDeployment) + .ThenInclude(d => d.Cluster) + .Where(b => b.RegisteredPostgresDatabaseId == databaseId) + .ToListAsync(ct); + + foreach (DatabaseBinding binding in bindings) + { + try + { + await k8sFactory.DeleteManifestAsync( + "Secret", binding.KubernetesSecretName, + binding.AppDeployment.Namespace, + binding.AppDeployment.Cluster.Kubeconfig!, ct); + } + catch { } + } + + await DeleteDatabaseSecretsAsync(tenantId, databaseId, ct); + + db.RegisteredPostgresDatabases.Remove(database); + await db.SaveChangesAsync(ct); + } + + /// + /// Removes a database from EntKube without running any SQL on the server. + /// Use when the database should be untracked (e.g. it was imported and the user + /// only wants to remove it from EntKube, not drop it from the actual Postgres server). + /// + public async Task UnregisterDatabaseAsync( + Guid tenantId, Guid instanceId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .Include(i => i.KubernetesCluster) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.RegisteredPostgresInstanceId == instance.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + await DeleteDatabaseSecretsAsync(tenantId, databaseId, ct); + + db.RegisteredPostgresDatabases.Remove(database); + await db.SaveChangesAsync(ct); + } + + // ──────── Credentials ──────── + + /// + /// Returns decrypted connection credentials for a database (HOST, PORT, DATABASE, + /// USERNAME, PASSWORD). + /// + public async Task> GetDatabaseCredentialsAsync( + Guid tenantId, Guid databaseId, CancellationToken ct = default) + { + List secrets = await vaultService.GetRegisteredPostgresDatabaseSecretsAsync(tenantId, databaseId, ct); + + Dictionary credentials = new(); + + foreach (VaultSecret secret in secrets) + { + string? value = await vaultService.GetSecretValueByIdAsync(secret.Id, ct); + if (value is not null) credentials[secret.Name] = value; + } + + return credentials; + } + + /// + /// Syncs the database credentials to Kubernetes as an Opaque Secret in the + /// instance namespace, and to every bound app deployment namespace. + /// + public async Task SyncCredentialsToK8sAsync( + Guid tenantId, Guid instanceId, Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .Include(i => i.KubernetesCluster) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.RegisteredPostgresInstanceId == instance.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + Dictionary credentials = await GetDatabaseCredentialsAsync(tenantId, databaseId, ct); + + if (credentials.Count == 0) + throw new InvalidOperationException("No credentials found in the vault for this database."); + + string primarySecretName = $"{instance.ServiceName}-{database.Name}-credentials"; + string kubeconfig = instance.KubernetesCluster.Kubeconfig!; + + await ApplyCredentialSecretAsync(credentials, primarySecretName, instance.Namespace, kubeconfig, ct); + + List vaultSecrets = await vaultService.GetRegisteredPostgresDatabaseSecretsAsync(tenantId, databaseId, ct); + foreach (VaultSecret s in vaultSecrets) + { + await vaultService.ConfigureKubernetesSyncAsync(s.Id, true, primarySecretName, instance.Namespace, ct); + } + + List bindings = await db.DatabaseBindings + .Include(b => b.AppDeployment) + .ThenInclude(d => d.Cluster) + .Where(b => b.RegisteredPostgresDatabaseId == databaseId && b.SyncEnabled) + .ToListAsync(ct); + + foreach (DatabaseBinding binding in bindings) + { + await k8sFactory.EnsureNamespaceAsync(binding.AppDeployment.Namespace, binding.AppDeployment.Cluster.Kubeconfig!, ct); + await ApplyCredentialSecretAsync( + credentials, binding.KubernetesSecretName, + binding.AppDeployment.Namespace, binding.AppDeployment.Cluster.Kubeconfig!, ct); + binding.LastSyncedAt = DateTime.UtcNow; + } + + await db.SaveChangesAsync(ct); + } + + /// + /// Rotates the owner role password. Runs ALTER ROLE on the server, updates the + /// vault, and syncs credentials to all bound namespaces. + /// + public async Task RotatePasswordAsync( + Guid tenantId, Guid instanceId, Guid databaseId, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstOrDefaultAsync(d => d.Id == databaseId && d.RegisteredPostgresInstanceId == instance.Id, ct) + ?? throw new InvalidOperationException("Database not found."); + + string newPassword = GeneratePassword(); + + await k8sFactory.ExecuteSqlOnPodAsync( + instance.AdminPodName, instance.Namespace, + $"ALTER ROLE \"{database.Owner}\" PASSWORD '{newPassword}';", + cluster.Kubeconfig!, instance.AdminUsername, adminPassword, ct); + + string k8sSecretName = $"{instance.ServiceName}-{database.Name}-credentials"; + + await vaultService.SetRegisteredPostgresDatabaseSecretAsync( + tenantId, databaseId, "PASSWORD", newPassword, k8sSecretName, instance.Namespace, ct); + + await SyncCredentialsToK8sAsync(tenantId, instanceId, databaseId, ct); + } + + // ──────── Dump / Restore ──────── + + /// + /// Returns all pg_dump backups for a specific database, newest first. + /// + public async Task> GetDumpsAsync( + Guid databaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.RegisteredPostgresDumps + .Include(d => d.StorageLink) + .Where(d => d.RegisteredPostgresDatabaseId == databaseId) + .OrderByDescending(d => d.CreatedAt) + .ToListAsync(ct); + } + + /// + /// Runs pg_dump on the admin pod and uploads the plain-SQL result to S3. + /// + public async Task CreateDumpAsync( + Guid tenantId, Guid instanceId, Guid databaseId, Guid storageLinkId, + CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstAsync(d => d.Id == databaseId, ct); + + StorageLink storageLink = await db.StorageLinks + .FirstAsync(sl => sl.Id == storageLinkId, ct); + + string dumpSql = await k8sFactory.RunCommandOnPodAsync( + instance.AdminPodName, instance.Namespace, + ["pg_dump", "--format=plain", "-U", instance.AdminUsername, "-h", "127.0.0.1", "-d", database.Name], + cluster.Kubeconfig!, + new Dictionary { ["PGPASSWORD"] = adminPassword }, + ct); + + byte[] bytes = Encoding.UTF8.GetBytes(dumpSql); + string key = $"pg-dumps/{SanitizeS3Name(instance.Name)}/{SanitizeS3Name(database.Name)}/{DateTime.UtcNow:yyyy-MM-ddTHH-mm-ss}Z.sql"; + + using MemoryStream stream = new(bytes); + await storageBrowserService.UploadAsync(tenantId, storageLink, key, stream, "text/plain", ct); + + RegisteredPostgresDump dump = new() + { + RegisteredPostgresDatabaseId = databaseId, + StorageLinkId = storageLinkId, + S3Key = key, + SizeBytes = bytes.Length + }; + + db.RegisteredPostgresDumps.Add(dump); + await db.SaveChangesAsync(ct); + dump.StorageLink = storageLink; + return dump; + } + + /// + /// Deletes the S3 object and the dump record. + /// + public async Task DeleteDumpAsync(Guid dumpId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDump dump = await db.RegisteredPostgresDumps + .Include(d => d.StorageLink) + .Include(d => d.RegisteredPostgresDatabase) + .ThenInclude(d => d.RegisteredPostgresInstance) + .FirstAsync(d => d.Id == dumpId, ct); + + Guid tenantId = dump.RegisteredPostgresDatabase.RegisteredPostgresInstance.TenantId; + + try { await storageBrowserService.DeleteAsync(tenantId, dump.StorageLink, dump.S3Key, ct); } + catch { } + + db.RegisteredPostgresDumps.Remove(dump); + await db.SaveChangesAsync(ct); + } + + /// + /// Downloads a dump from S3 and restores it into a new database on a CNPG cluster. + /// Creates the target database, then pipes the SQL via psql stdin. + /// + public async Task RestoreDumpToCnpgAsync( + Guid dumpId, Guid cnpgClusterId, string newDatabaseName, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDump dump = await db.RegisteredPostgresDumps + .Include(d => d.StorageLink) + .Include(d => d.RegisteredPostgresDatabase) + .ThenInclude(d => d.RegisteredPostgresInstance) + .FirstAsync(d => d.Id == dumpId, ct); + + Guid tenantId = dump.RegisteredPostgresDatabase.RegisteredPostgresInstance.TenantId; + + byte[] dumpBytes = await storageBrowserService.DownloadAsync(tenantId, dump.StorageLink, dump.S3Key, ct); + string dumpSql = Encoding.UTF8.GetString(dumpBytes); + + CnpgCluster cnpgCluster = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstAsync(c => c.Id == cnpgClusterId, ct); + + await RestoreSqlToCnpgAsync(tenantId, dumpSql, cnpgCluster, newDatabaseName, ct); + } + + /// + /// Runs pg_dump on the registered postgres pod and pipes the result + /// directly into a new CNPG database — no S3 storage needed. + /// --no-owner and --no-privileges ensure the dump is portable: owner roles + /// from the source cluster won't exist on CNPG, so we skip those statements + /// and let the postgres superuser own everything during restore. + /// + public async Task MigrateDirectlyToCnpgAsync( + Guid tenantId, Guid instanceId, Guid databaseId, + Guid cnpgClusterId, string newDatabaseName, CancellationToken ct = default) + { + (RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword) = + await LoadInstanceAsync(tenantId, instanceId, ct); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresDatabase database = await db.RegisteredPostgresDatabases + .FirstAsync(d => d.Id == databaseId, ct); + + string dumpSql = await k8sFactory.RunCommandOnPodAsync( + instance.AdminPodName, instance.Namespace, + [ + "pg_dump", "--format=plain", "--no-owner", "--no-privileges", + "-U", instance.AdminUsername, "-h", "127.0.0.1", "-d", database.Name + ], + cluster.Kubeconfig!, + new Dictionary { ["PGPASSWORD"] = adminPassword }, + ct); + + CnpgCluster cnpgCluster = await db.CnpgClusters + .Include(c => c.KubernetesCluster) + .FirstAsync(c => c.Id == cnpgClusterId, ct); + + await RestoreSqlToCnpgAsync(tenantId, dumpSql, cnpgCluster, newDatabaseName, ct); + } + + private async Task RestoreSqlToCnpgAsync( + Guid tenantId, string dumpSql, CnpgCluster cnpgCluster, string newDatabaseName, CancellationToken ct) + { + string kubeconfig = cnpgCluster.KubernetesCluster.Kubeconfig!; + string owner = $"{newDatabaseName}_owner"; + string password = GeneratePassword(); + + // Create the owner role and the database on the CNPG cluster. + await k8sFactory.ExecuteSqlAsync( + cnpgCluster.Name, cnpgCluster.Namespace, + $""" + CREATE ROLE "{owner}" WITH LOGIN PASSWORD '{password}'; + CREATE DATABASE "{newDatabaseName}" OWNER "{owner}"; + """, + kubeconfig, ct); + + // Restore the dump connected directly to the new database. + // Using ExecuteSqlInCnpgDatabaseAsync (psql -d {db}) instead of \c avoids + // the silent failure that occurs when \c cannot reconnect in stdin mode + // (psql continues reading in the wrong database and exits with code 0). + await k8sFactory.ExecuteSqlInCnpgDatabaseAsync( + cnpgCluster.Name, cnpgCluster.Namespace, + newDatabaseName, dumpSql, kubeconfig, ct); + + // Register the database in EntKube so it appears in the UI and credentials + // can be managed and synced to Kubernetes Secrets. + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + CnpgDatabase cnpgDb = new() + { + Id = Guid.NewGuid(), + CnpgClusterId = cnpgCluster.Id, + Name = newDatabaseName, + Owner = owner, + Status = CnpgDatabaseStatus.Ready + }; + + db.CnpgDatabases.Add(cnpgDb); + await db.SaveChangesAsync(ct); + + string k8sSecretName = $"{cnpgCluster.Name}-{newDatabaseName}-credentials"; + string host = $"{cnpgCluster.Name}-rw.{cnpgCluster.Namespace}.svc.cluster.local"; + + await vaultService.InitializeVaultAsync(tenantId, ct); + await vaultService.SetCnpgDatabaseSecretAsync(tenantId, cnpgDb.Id, "HOST", host, k8sSecretName, cnpgCluster.Namespace, ct); + await vaultService.SetCnpgDatabaseSecretAsync(tenantId, cnpgDb.Id, "PORT", "5432", k8sSecretName, cnpgCluster.Namespace, ct); + await vaultService.SetCnpgDatabaseSecretAsync(tenantId, cnpgDb.Id, "DATABASE", newDatabaseName, k8sSecretName, cnpgCluster.Namespace, ct); + await vaultService.SetCnpgDatabaseSecretAsync(tenantId, cnpgDb.Id, "USERNAME", owner, k8sSecretName, cnpgCluster.Namespace, ct); + await vaultService.SetCnpgDatabaseSecretAsync(tenantId, cnpgDb.Id, "PASSWORD", password, k8sSecretName, cnpgCluster.Namespace, ct); + } + + // ──────── Private Helpers ──────── + + private async Task<(RegisteredPostgresInstance instance, KubernetesCluster cluster, string adminPassword)> + LoadInstanceAsync(Guid tenantId, Guid instanceId, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance instance = await db.RegisteredPostgresInstances + .Include(i => i.KubernetesCluster) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct) + ?? throw new InvalidOperationException("Registered Postgres instance not found."); + + string adminPassword = await vaultService.GetRegisteredPostgresAdminPasswordAsync(tenantId, instanceId, ct) + ?? throw new InvalidOperationException("Admin password not found in vault."); + + return (instance, instance.KubernetesCluster, adminPassword); + } + + private async Task TestConnectionCoreAsync( + string podName, string ns, string username, string password, + string kubeconfig, CancellationToken ct) + { + try + { + await k8sFactory.ExecuteSqlOnPodAsync( + podName, ns, "SELECT 1;", kubeconfig, username, password, ct); + } + catch (Exception ex) + { + throw new InvalidOperationException( + $"Could not connect to Postgres on pod {podName}: {ex.Message}", ex); + } + } + + private Task StoreDatabaseSecretAsync( + Guid tenantId, Guid databaseId, string name, string value, + string k8sSecretName, string k8sNamespace, CancellationToken ct) => + vaultService.SetRegisteredPostgresDatabaseSecretAsync( + tenantId, databaseId, name, value, k8sSecretName, k8sNamespace, ct); + + private async Task DeleteDatabaseSecretsAsync(Guid tenantId, Guid databaseId, CancellationToken ct) + { + List secrets = await vaultService.GetRegisteredPostgresDatabaseSecretsAsync(tenantId, databaseId, ct); + foreach (VaultSecret s in secrets) + { + await vaultService.DeleteSecretAsync(s.Id, ct); + } + } + + private async Task ApplyCredentialSecretAsync( + Dictionary credentials, string secretName, string ns, + string kubeconfig, CancellationToken ct) + { + StringBuilder sb = new(); + sb.AppendLine("apiVersion: v1"); + sb.AppendLine("kind: Secret"); + sb.AppendLine("metadata:"); + sb.AppendLine($" name: {secretName}"); + sb.AppendLine($" namespace: {ns}"); + sb.AppendLine("type: Opaque"); + sb.AppendLine("data:"); + + foreach (KeyValuePair kvp in credentials) + { + string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(kvp.Value)); + sb.AppendLine($" {kvp.Key}: {encoded}"); + } + + await k8sFactory.ApplyManifestAsync(sb.ToString(), kubeconfig, ct); + } + + private static List ParsePsqlColumnOutput(string output) => + output.Split('\n', StringSplitOptions.RemoveEmptyEntries) + .Select(l => l.Trim()) + .Where(l => l.Length > 0 && l != "datname" && !l.StartsWith("---") && !l.StartsWith("(")) + .ToList(); + + private static string SanitizeS3Name(string name) => + Regex.Replace(name.ToLowerInvariant(), @"[^a-z0-9\-]", "-"); + + private static string GeneratePassword() + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return string.Create(24, chars, static (span, c) => + { + Span bytes = stackalloc byte[24]; + RandomNumberGenerator.Fill(bytes); + for (int i = 0; i < span.Length; i++) + span[i] = c[bytes[i] % c.Length]; + }); + } + + /// + /// Queries Kubernetes for all pods belonging to a registered instance. + /// + /// Primary strategy: Helm release labels (app.kubernetes.io/instance + name). + /// Bitnami HA and similar charts split primary/read into separate StatefulSets + /// that share the same Helm labels, so a label selector is the only way to get + /// all pods across both StatefulSets in one query. + /// + /// Fallback: StatefulSet ordinal names ({stsName}-0, -1, …). + /// Used for non-Helm StatefulSets where Helm labels are absent. + /// + public async Task> GetPodsAsync( + Guid tenantId, Guid instanceId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + RegisteredPostgresInstance? instance = await db.Set() + .Include(i => i.KubernetesCluster) + .FirstOrDefaultAsync(i => i.Id == instanceId && i.TenantId == tenantId, ct); + + if (instance is null || string.IsNullOrWhiteSpace(instance.KubernetesCluster.Kubeconfig)) + return []; + + string kubeconfig = instance.KubernetesCluster.Kubeconfig; + string adminPodJson; + + try + { + adminPodJson = await k8sFactory.GetJsonAsync( + $"pod/{instance.AdminPodName}", instance.Namespace, kubeconfig, ct: ct); + } + catch + { + return []; + } + + // ── Strategy 1: Helm release labels (covers Bitnami HA with separate primary/read StatefulSets) ── + try + { + string? helmInstance = GetPodLabel(adminPodJson, "app.kubernetes.io/instance"); + string? helmName = GetPodLabel(adminPodJson, "app.kubernetes.io/name"); + + if (helmInstance is not null && helmName is not null) + { + string selector = $"app.kubernetes.io/instance={helmInstance},app.kubernetes.io/name={helmName}"; + string podsJson = await k8sFactory.GetJsonAsync( + "pods", instance.Namespace, kubeconfig, selector, ct); + List helmPods = ParsePodList(podsJson, isList: true); + if (helmPods.Count > 0) + return helmPods; + } + } + catch { } + + // ── Strategy 2: StatefulSet ordinal names (non-Helm StatefulSets with replicas > 1) ── + try + { + string? stsName = GetStatefulSetOwnerName(adminPodJson); + if (stsName is not null) + { + string stsJson = await k8sFactory.GetJsonAsync( + $"statefulset/{stsName}", instance.Namespace, kubeconfig, ct: ct); + int replicas = GetStatefulSetReplicas(stsJson); + + if (replicas > 1) + { + Task[] tasks = Enumerable.Range(0, replicas) + .Select(i => k8sFactory.GetJsonAsync( + $"pod/{stsName}-{i}", instance.Namespace, kubeconfig, ct: ct)) + .ToArray(); + string[] podJsons = await Task.WhenAll(tasks); + + List result = []; + foreach (string podJson in podJsons) + result.AddRange(ParsePodList(podJson, isList: false)); + if (result.Count > 0) + return result; + } + } + } + catch { } + + return ParsePodList(adminPodJson, isList: false); + } + + private static string? GetPodLabel(string podJson, string labelKey) + { + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(podJson); + if (doc.RootElement.TryGetProperty("metadata", out System.Text.Json.JsonElement meta) + && meta.TryGetProperty("labels", out System.Text.Json.JsonElement labels) + && labels.TryGetProperty(labelKey, out System.Text.Json.JsonElement val)) + return val.GetString(); + return null; + } + + private static string? GetStatefulSetOwnerName(string podJson) + { + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(podJson); + if (!doc.RootElement.TryGetProperty("metadata", out System.Text.Json.JsonElement meta) + || !meta.TryGetProperty("ownerReferences", out System.Text.Json.JsonElement owners)) + return null; + + foreach (System.Text.Json.JsonElement owner in owners.EnumerateArray()) + { + if (owner.TryGetProperty("kind", out System.Text.Json.JsonElement kind) + && kind.GetString() == "StatefulSet" + && owner.TryGetProperty("name", out System.Text.Json.JsonElement name)) + return name.GetString(); + } + return null; + } + + private static int GetStatefulSetReplicas(string stsJson) + { + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(stsJson); + if (doc.RootElement.TryGetProperty("spec", out System.Text.Json.JsonElement spec) + && spec.TryGetProperty("replicas", out System.Text.Json.JsonElement replicas)) + return replicas.GetInt32(); + return 1; + } + + private static List ParsePodList(string json, bool isList) + { + using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(json); + System.Text.Json.JsonElement root = doc.RootElement; + + System.Collections.Generic.IEnumerable items = isList + && root.TryGetProperty("items", out System.Text.Json.JsonElement itemsEl) + ? itemsEl.EnumerateArray() + : [root]; + + List pods = []; + foreach (System.Text.Json.JsonElement item in items) + { + string podName = item.GetProperty("metadata").GetProperty("name").GetString() ?? ""; + string podStatus = "Unknown"; + bool ready = false; + string? node = null; + DateTime? startTime = null; + int restarts = 0; + + if (item.TryGetProperty("spec", out System.Text.Json.JsonElement spec) + && spec.TryGetProperty("nodeName", out System.Text.Json.JsonElement nodeName)) + node = nodeName.GetString(); + + if (item.TryGetProperty("status", out System.Text.Json.JsonElement statusEl)) + { + if (statusEl.TryGetProperty("phase", out System.Text.Json.JsonElement phaseEl)) + podStatus = phaseEl.GetString() ?? "Unknown"; + + if (statusEl.TryGetProperty("startTime", out System.Text.Json.JsonElement startEl) + && DateTime.TryParse(startEl.GetString(), null, + System.Globalization.DateTimeStyles.RoundtripKind, out DateTime parsed)) + startTime = parsed; + + if (statusEl.TryGetProperty("containerStatuses", out System.Text.Json.JsonElement containers)) + { + foreach (System.Text.Json.JsonElement container in containers.EnumerateArray()) + { + if (container.TryGetProperty("restartCount", out System.Text.Json.JsonElement rc)) + restarts += rc.GetInt32(); + if (container.TryGetProperty("ready", out System.Text.Json.JsonElement readyEl)) + ready = ready || readyEl.GetBoolean(); + } + } + } + + pods.Add(new CnpgPodInfo + { + Name = podName, + Role = "primary", + Status = podStatus, + Ready = ready, + Node = node, + StartTime = startTime, + Restarts = restarts + }); + } + return pods; + } +} diff --git a/src/EntKube.Web/Services/StorageBrowserService.cs b/src/EntKube.Web/Services/StorageBrowserService.cs new file mode 100644 index 0000000..e0cad11 --- /dev/null +++ b/src/EntKube.Web/Services/StorageBrowserService.cs @@ -0,0 +1,396 @@ +using Amazon; +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +/// +/// An object (file) returned from a bucket listing. +/// +public class StorageObject +{ + public required string Key { get; set; } + public required string Prefix { get; set; } + + public string Name => Key.Length > Prefix.Length + ? Key[Prefix.Length..] + : Key; + + public long SizeBytes { get; set; } + public DateTime LastModified { get; set; } + public string? ETag { get; set; } +} + +/// +/// Result of listing a bucket prefix — folders (common prefixes) plus objects. +/// +public class StorageBrowseResult +{ + public List Folders { get; set; } = []; + public List Objects { get; set; } = []; +} + +/// +/// Provides file-level CRUD operations on any S3-compatible storage link: +/// AWS S3, MinIO, and Cleura S3. Credentials are retrieved from the vault. +/// Downloads are buffered in memory; the maximum file size is 100 MB. +/// +public class StorageBrowserService(VaultService vaultService, IDbContextFactory dbFactory) +{ + private const long MaxDownloadBytes = 100L * 1024 * 1024; + + // ── S3 Client Factory ── + + /// + /// Creates an S3 client for the given storage link. + /// MinIO links are routed through the Kubernetes API server proxy so that + /// cluster-internal service URLs (*.svc.cluster.local) are reachable. + /// Returns the client plus an optional IDisposable to clean up the K8s client + /// (non-null only for MinIO proxy clients). + /// + private async Task<(AmazonS3Client Client, IDisposable? Cleanup)> GetClientAsync( + Guid tenantId, StorageLink link, CancellationToken ct) + { + string? accessKey = await vaultService.GetStorageLinkSecretValueAsync( + tenantId, link.Id, "ACCESS_KEY", ct); + string? secretKey = await vaultService.GetStorageLinkSecretValueAsync( + tenantId, link.Id, "SECRET_KEY", ct); + + if (string.IsNullOrWhiteSpace(accessKey) || string.IsNullOrWhiteSpace(secretKey)) + { + throw new InvalidOperationException( + "Storage credentials not found in vault. Ensure ACCESS_KEY and SECRET_KEY are configured."); + } + + // MinIO: proxy via K8s API server. + if (link.Provider == StorageProvider.MinIO && link.Endpoint is not null) + { + string? kubeconfig = await ResolveKubeconfigAsync(link, ct); + + if (!string.IsNullOrWhiteSpace(kubeconfig)) + { + ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + return (proxied.S3, proxied); + } + } + + BasicAWSCredentials credentials = new(accessKey, secretKey); + + // Standard AWS S3 — use regional endpoint, no custom ServiceURL. + if (link.Provider == StorageProvider.AwsS3 && string.IsNullOrWhiteSpace(link.Endpoint)) + { + AmazonS3Config config = new() + { + RegionEndpoint = RegionEndpoint.GetBySystemName(link.Region ?? "us-east-1"), + Timeout = TimeSpan.FromSeconds(30), + MaxErrorRetry = 1 + }; + return (new AmazonS3Client(credentials, config), null); + } + + // Cleura S3 / AWS with custom endpoint — force path-style addressing. + string endpoint = link.Endpoint + ?? throw new InvalidOperationException("Endpoint URL is required for this storage provider."); + + AmazonS3Config s3Config = new() + { + ServiceURL = endpoint, + ForcePathStyle = true, + AuthenticationRegion = link.Region ?? "us-east-1", + UseHttp = endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase), + Timeout = TimeSpan.FromSeconds(30), + MaxErrorRetry = 1 + }; + + return (new AmazonS3Client(credentials, s3Config), null); + } + + /// + /// Resolves the kubeconfig for the cluster that hosts a MinIO storage link. + /// Uses ComponentId when set; falls back to a namespace-matched query for + /// older links registered before ComponentId tracking was introduced. + /// + private async Task ResolveKubeconfigAsync(StorageLink link, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Fast path: ComponentId set at registration. + if (link.ComponentId is not null) + { + string? kc = await db.ClusterComponents + .Where(c => c.Id == link.ComponentId.Value) + .Select(c => c.Cluster.Kubeconfig) + .FirstOrDefaultAsync(ct); + if (!string.IsNullOrWhiteSpace(kc)) + return kc; + } + + // Fallback: find a MinIO component on any cluster for this tenant, + // preferring one whose namespace matches the endpoint namespace. + var parsed = KubernetesS3Proxy.ParseInternalServiceUrl(link.Endpoint ?? ""); + + IQueryable query = db.ClusterComponents + .Include(c => c.Cluster) + .Where(c => c.Cluster.TenantId == link.TenantId + && (c.Name == "minio-operator" || c.Name == "minio") + && c.Status == ComponentStatus.Installed + && c.Cluster.Kubeconfig != null); + + if (parsed is not null) + query = query.OrderByDescending(c => c.Namespace == parsed.Value.Namespace); + + ClusterComponent? component = await query.FirstOrDefaultAsync(ct); + return component?.Cluster?.Kubeconfig; + } + + private static string RequireBucket(StorageLink link) => + link.BucketName ?? throw new InvalidOperationException("Bucket name is not configured for this storage link."); + + // ── List ── + + /// + /// Lists objects and "folders" (common prefixes) at the given prefix. + /// Pass an empty string to list the bucket root. + /// + public async Task ListAsync( + Guid tenantId, StorageLink link, string prefix, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + + ListObjectsV2Request request = new() + { + BucketName = bucket, + Prefix = prefix, + Delimiter = "/", + MaxKeys = 1000 + }; + + ListObjectsV2Response response = await client.ListObjectsV2Async(request, ct); + + StorageBrowseResult result = new(); + + foreach (string? cp in response.CommonPrefixes ?? []) + { + if (cp is not null) + result.Folders.Add(cp); + } + + foreach (S3Object obj in response.S3Objects ?? []) + { + string? key = obj.Key; + if (key is null || key == prefix) + continue; // skip null keys and folder markers + + result.Objects.Add(new StorageObject + { + Key = key, + Prefix = prefix, + SizeBytes = obj.Size ?? 0, + LastModified = obj.LastModified ?? DateTime.MinValue, + ETag = obj.ETag?.Trim('"') + }); + } + + return result; + } + + // ── Upload ── + + /// + /// Uploads a stream to the given key in the bucket. + /// + public async Task UploadAsync( + Guid tenantId, + StorageLink link, + string key, + Stream stream, + string contentType, + CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + + PutObjectRequest request = new() + { + BucketName = bucket, + Key = key, + InputStream = stream, + ContentType = contentType, + AutoCloseStream = false + }; + + await client.PutObjectAsync(request, ct); + } + + // ── Download ── + + /// + /// Downloads an object and returns its bytes. Maximum 100 MB. + /// + public async Task DownloadAsync( + Guid tenantId, StorageLink link, string key, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + + GetObjectRequest request = new() { BucketName = bucket, Key = key }; + using GetObjectResponse response = await client.GetObjectAsync(request, ct); + + if (response.ContentLength > MaxDownloadBytes) + { + throw new InvalidOperationException( + $"File is too large to download through the browser " + + $"({response.ContentLength / 1024 / 1024} MB). Maximum is {MaxDownloadBytes / 1024 / 1024} MB."); + } + + using MemoryStream ms = new(); + await response.ResponseStream.CopyToAsync(ms, ct); + return ms.ToArray(); + } + + // ── Delete ── + + /// + /// Deletes a single object. + /// + public async Task DeleteAsync( + Guid tenantId, StorageLink link, string key, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + await client.DeleteObjectAsync(bucket, key, ct); + } + + /// + /// Deletes multiple objects in S3 batch delete requests (max 1000 per request). + /// + public async Task DeleteManyAsync( + Guid tenantId, StorageLink link, IEnumerable keys, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + + List keyVersions = keys.Select(k => new KeyVersion { Key = k }).ToList(); + + for (int i = 0; i < keyVersions.Count; i += 1000) + { + DeleteObjectsRequest deleteRequest = new() + { + BucketName = bucket, + Objects = keyVersions.Skip(i).Take(1000).ToList() + }; + await client.DeleteObjectsAsync(deleteRequest, ct); + } + } + + /// + /// Deletes all objects under a prefix (folder), including the folder marker itself. + /// Paginates through all objects to handle folders with more than 1000 items. + /// + public async Task DeleteFolderAsync( + Guid tenantId, StorageLink link, string folderPrefix, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + string prefix = folderPrefix.TrimEnd('/') + '/'; + string? continuationToken = null; + + do + { + ListObjectsV2Request listRequest = new() + { + BucketName = bucket, + Prefix = prefix, + MaxKeys = 1000, + ContinuationToken = continuationToken + }; + + ListObjectsV2Response listResponse = await client.ListObjectsV2Async(listRequest, ct); + + List objects = listResponse.S3Objects ?? []; + if (objects.Count > 0) + { + DeleteObjectsRequest deleteRequest = new() + { + BucketName = bucket, + Objects = objects + .Where(o => o.Key is not null) + .Select(o => new KeyVersion { Key = o.Key }) + .ToList() + }; + await client.DeleteObjectsAsync(deleteRequest, ct); + } + + continuationToken = listResponse.IsTruncated == true ? listResponse.NextContinuationToken : null; + } + while (continuationToken is not null); + } + + // ── Create Folder ── + + /// + /// Creates a "folder" by uploading a zero-byte object with a trailing slash key. + /// + public async Task CreateFolderAsync( + Guid tenantId, StorageLink link, string folderPath, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + string key = folderPath.TrimEnd('/') + '/'; + + PutObjectRequest request = new() + { + BucketName = bucket, + Key = key, + ContentBody = "" + }; + + await client.PutObjectAsync(request, ct); + } + + // ── Presigned URL ── + + /// + /// Generates a presigned GET URL for the given object, valid for hours. + /// The URL allows anyone with it to download the file directly from the storage provider + /// without credentials. For cluster-internal MinIO endpoints the URL will contain the + /// internal hostname and will only be reachable from within the cluster. + /// + public async Task GetPresignedUrlAsync( + Guid tenantId, StorageLink link, string key, int expiryHours = 1, CancellationToken ct = default) + { + (AmazonS3Client client, IDisposable? cleanup) = await GetClientAsync(tenantId, link, ct); + using AmazonS3Client _ = client; + using IDisposable? __ = cleanup; + string bucket = RequireBucket(link); + + GetPreSignedUrlRequest request = new() + { + BucketName = bucket, + Key = key, + Expires = DateTime.UtcNow.AddHours(expiryHours), + Verb = HttpVerb.GET + }; + + return await client.GetPreSignedURLAsync(request); + } +} diff --git a/src/EntKube.Web/Services/StorageService.cs b/src/EntKube.Web/Services/StorageService.cs index f412a80..bdc2a55 100644 --- a/src/EntKube.Web/Services/StorageService.cs +++ b/src/EntKube.Web/Services/StorageService.cs @@ -1,6 +1,7 @@ using System.Text.Json; using EntKube.Web.Data; using k8s; +using k8s.Models; using Microsoft.EntityFrameworkCore; namespace EntKube.Web.Services; @@ -17,8 +18,11 @@ public class MinioBucketInfo public string? Endpoint { get; set; } public string? Storage { get; set; } public string? Status { get; set; } + /// "green", "yellow", or "red" from .status.healthStatus. Null on older operator versions. + public string? HealthStatus { get; set; } public DateTime? CreatedAt { get; set; } public Guid ClusterId { get; set; } + public List Pods { get; set; } = []; } /// @@ -44,7 +48,7 @@ public class StorageService(IDbContextFactory dbFactory, V return await db.ClusterComponents .AnyAsync(c => c.Cluster.TenantId == tenantId - && c.Name == "minio" + && (c.Name == "minio" || c.Name == "minio-operator") && c.Status == ComponentStatus.Installed, ct); } @@ -66,7 +70,7 @@ public class StorageService(IDbContextFactory dbFactory, V .Include(k => k.Environment) .Include(k => k.Components) .Where(k => k.TenantId == tenantId - && k.Components.Any(c => c.Name == "minio" && c.Status == ComponentStatus.Installed)) + && k.Components.Any(c => (c.Name == "minio" || c.Name == "minio-operator") && c.Status == ComponentStatus.Installed)) .ToListAsync(ct); List results = []; @@ -116,6 +120,41 @@ public class StorageService(IDbContextFactory dbFactory, V foreach (JsonElement item in items.EnumerateArray()) { MinioBucketInfo info = ParseMinioTenant(item, cluster); + + try + { + V1PodList podList = await client.CoreV1.ListNamespacedPodAsync( + info.Namespace, + labelSelector: $"v1.min.io/tenant={info.Name}", + cancellationToken: ct); + + info.Pods = podList.Items.Select(pod => new PodInfo + { + Name = pod.Metadata.Name, + Namespace = info.Namespace, + Status = pod.Status?.Phase ?? "Unknown", + ReadyContainers = pod.Status?.ContainerStatuses?.Count(cs => cs.Ready) ?? 0, + TotalContainers = pod.Spec?.Containers?.Count ?? 0, + Restarts = pod.Status?.ContainerStatuses?.Sum(cs => cs.RestartCount) ?? 0, + StartTime = pod.Status?.StartTime, + Containers = (pod.Status?.ContainerStatuses ?? []).Select(cs => new ContainerInfo + { + Name = cs.Name, + Image = cs.Image ?? "", + Ready = cs.Ready, + RestartCount = cs.RestartCount, + State = cs.State?.Running is not null ? "Running" + : cs.State?.Waiting is not null ? $"Waiting: {cs.State.Waiting.Reason}" + : cs.State?.Terminated is not null ? $"Terminated: {cs.State.Terminated.Reason}" + : "Unknown" + }).ToList() + }).ToList(); + } + catch + { + // Pod listing failed — leave Pods empty, status badge is still shown. + } + results.Add(info); } } @@ -152,19 +191,41 @@ public class StorageService(IDbContextFactory dbFactory, V } string status = "Unknown"; + string? healthStatus = null; - if (item.TryGetProperty("status", out JsonElement statusEl) - && statusEl.TryGetProperty("currentState", out JsonElement stateEl)) + if (item.TryGetProperty("status", out JsonElement statusEl)) { - status = stateEl.GetString() ?? "Unknown"; + if (statusEl.TryGetProperty("currentState", out JsonElement stateEl)) + status = stateEl.GetString() ?? "Unknown"; + + if (statusEl.TryGetProperty("healthStatus", out JsonElement healthEl)) + healthStatus = healthEl.GetString(); } DateTime? createdAt = metadata.TryGetProperty("creationTimestamp", out JsonElement tsEl) ? tsEl.GetDateTime() : null; - // The MinIO service endpoint within the cluster. + // The MinIO operator creates a service named after the tenant CR (e.g. "my-tenant.my-ns.svc..."). + // Standalone MinIO also uses the release name. Either way, the service name matches the CR/release name. + // + // TLS detection: MinIO operator enables TLS by default (requestAutoCert: true). + // Only use http:// if requestAutoCert is explicitly false AND no external cert secret is configured. + bool tlsEnabled = true; + if (item.TryGetProperty("spec", out JsonElement tlsSpec)) + { + if (tlsSpec.TryGetProperty("requestAutoCert", out JsonElement autocertEl) + && autocertEl.ValueKind == JsonValueKind.False) + { + // requestAutoCert disabled — only plain HTTP if no external cert is set either. + bool hasExternalCert = tlsSpec.TryGetProperty("externalCertSecret", out JsonElement extCert) + && extCert.ValueKind != JsonValueKind.Null + && extCert.ValueKind != JsonValueKind.Undefined; + tlsEnabled = hasExternalCert; + } + } - string endpoint = $"http://minio.{ns}.svc.cluster.local:9000"; + string scheme = tlsEnabled ? "https" : "http"; + string endpoint = $"{scheme}://{name}.{ns}.svc.cluster.local:9000"; return new MinioBucketInfo { @@ -175,6 +236,7 @@ public class StorageService(IDbContextFactory dbFactory, V Endpoint = endpoint, Storage = storage, Status = status, + HealthStatus = healthStatus, CreatedAt = createdAt, ClusterId = cluster.Id }; @@ -192,6 +254,7 @@ public class StorageService(IDbContextFactory dbFactory, V IQueryable query = db.StorageLinks .Include(s => s.Environment) + .Include(s => s.Component) .Where(s => s.TenantId == tenantId); if (environmentId.HasValue) @@ -257,6 +320,58 @@ public class StorageService(IDbContextFactory dbFactory, V return link; } + /// + /// Registers a discovered MinIO Tenant as a StorageLink so it can be browsed + /// and managed like any other storage provider. Credentials are stored in the vault. + /// ComponentId is set to the minio/minio-operator component on the cluster so the + /// link is scoped to a specific cluster, preventing false "already registered" matches + /// when multiple clusters share the same tenant namespace name. + /// + public async Task RegisterMinioTenantAsync( + Guid tenantId, + Guid environmentId, + Guid clusterId, + string name, + string endpoint, + string accessKey, + string secretKey, + string? notes, + CancellationToken ct = default) + { + StorageLink link = await CreateStorageLinkAsync( + tenantId, + environmentId, + StorageProvider.MinIO, + name, + endpoint, + bucketName: null, + region: "us-east-1", + accessKey, + secretKey, + notes, + ct: ct); + + // Attach the link to the minio operator/standalone component on this cluster so that + // IsMinioRegistered can distinguish tenants on different clusters by cluster identity. + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + ClusterComponent? component = await db.ClusterComponents + .FirstOrDefaultAsync(c => c.ClusterId == clusterId + && (c.Name == "minio-operator" || c.Name == "minio"), ct); + + if (component is not null) + { + StorageLink? stored = await db.StorageLinks.FindAsync([link.Id], ct); + if (stored is not null) + { + stored.ComponentId = component.Id; + await db.SaveChangesAsync(ct); + } + } + + return link; + } + /// /// Updates an existing storage link's metadata. Credentials are updated /// separately via the vault. @@ -442,10 +557,16 @@ public class StorageService(IDbContextFactory dbFactory, V && !string.IsNullOrEmpty(link.BucketName) && !string.IsNullOrEmpty(link.Region)) { - (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? accessKey = await vaultService.GetStorageLinkSecretValueAsync(tenantId, linkId, "ACCESS_KEY", ct); + string? secretKey = await vaultService.GetStorageLinkSecretValueAsync(tenantId, linkId, "SECRET_KEY", ct); - await openStackS3.DeleteBucketAsync( - link.Endpoint, accessKey, secretKey, link.BucketName, link.Region, ct); + if (!string.IsNullOrWhiteSpace(accessKey) && !string.IsNullOrWhiteSpace(secretKey)) + { + await openStackS3.DeleteBucketAsync( + link.Endpoint, accessKey, secretKey, link.BucketName, link.Region, ct); + } + // If credentials are missing the bucket cannot be deleted via the S3 API, + // but we still remove the StorageLink record so the link can be cleaned up. } // Remove the StorageLink and vault secrets. @@ -454,55 +575,123 @@ public class StorageService(IDbContextFactory dbFactory, V } /// - /// Gets the CORS configuration for a Cleura S3 bucket. + /// Lists all buckets accessible with the stored credentials for any S3-compatible link (MinIO or Cleura). + /// MinIO links are proxied through the Kubernetes API server; Cleura uses the S3 API directly. + /// + public async Task> ListS3BucketsAsync( + Guid tenantId, Guid linkId, CancellationToken ct = default) + { + StorageLink link = await GetS3LinkOrThrowAsync(tenantId, linkId, ct); + (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? kubeconfig = await ResolveMinioKubeconfigAsync(link, ct); + + if (kubeconfig is not null && link.Endpoint is not null) + { + using ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + try + { + Amazon.S3.Model.ListBucketsResponse resp = await proxied.S3.ListBucketsAsync(ct); + return (resp.Buckets ?? []) + .Select(b => new S3BucketInfo { Name = b.BucketName ?? "", CreatedAt = b.CreationDate.GetValueOrDefault() }) + .ToList(); + } + catch (Amazon.S3.AmazonS3Exception s3ex) + { + throw new InvalidOperationException( + $"MinIO S3 error [{(int)s3ex.StatusCode} {s3ex.ErrorCode}]: {s3ex.Message}", s3ex); + } + } + + return await openStackS3.ListBucketsAsync( + link.Endpoint ?? "", accessKey, secretKey, link.Region ?? "us-east-1", ct); + } + + /// + /// Gets the CORS configuration for an S3-compatible bucket (MinIO or Cleura). /// Returns null if no CORS configuration exists. /// public async Task?> GetBucketCorsAsync( Guid tenantId, Guid linkId, CancellationToken ct = default) { - StorageLink link = await GetCleuraLinkOrThrowAsync(tenantId, linkId, ct); + StorageLink link = await GetS3LinkOrThrowAsync(tenantId, linkId, ct); (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? kubeconfig = await ResolveMinioKubeconfigAsync(link, ct); + + if (kubeconfig is not null && link.Endpoint is not null) + { + using ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + return await openStackS3.GetBucketCorsAsync(proxied.S3, link.BucketName!, ct); + } return await openStackS3.GetBucketCorsAsync( link.Endpoint!, accessKey, secretKey, link.BucketName!, link.Region!, ct); } /// - /// Sets the CORS configuration for a Cleura S3 bucket. + /// Sets the CORS configuration for an S3-compatible bucket (MinIO or Cleura). /// Pass an empty list to remove all CORS rules. /// public async Task SetBucketCorsAsync( Guid tenantId, Guid linkId, List rules, CancellationToken ct = default) { - StorageLink link = await GetCleuraLinkOrThrowAsync(tenantId, linkId, ct); + StorageLink link = await GetS3LinkOrThrowAsync(tenantId, linkId, ct); (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? kubeconfig = await ResolveMinioKubeconfigAsync(link, ct); + + if (kubeconfig is not null && link.Endpoint is not null) + { + using ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + await openStackS3.SetBucketCorsAsync(proxied.S3, link.BucketName!, rules, ct); + return; + } await openStackS3.SetBucketCorsAsync( link.Endpoint!, accessKey, secretKey, link.BucketName!, link.Region!, rules, ct); } /// - /// Gets the bucket policy as a JSON string. + /// Gets the bucket policy as a JSON string for an S3-compatible link (MinIO or Cleura). /// Returns null if no policy is set. /// public async Task GetBucketPolicyAsync( Guid tenantId, Guid linkId, CancellationToken ct = default) { - StorageLink link = await GetCleuraLinkOrThrowAsync(tenantId, linkId, ct); + StorageLink link = await GetS3LinkOrThrowAsync(tenantId, linkId, ct); (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? kubeconfig = await ResolveMinioKubeconfigAsync(link, ct); + + if (kubeconfig is not null && link.Endpoint is not null) + { + using ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + return await openStackS3.GetBucketPolicyAsync(proxied.S3, link.BucketName!, ct); + } return await openStackS3.GetBucketPolicyAsync( link.Endpoint!, accessKey, secretKey, link.BucketName!, link.Region!, ct); } /// - /// Sets the bucket policy from a JSON string. Pass null to remove the policy. + /// Sets the bucket policy from a JSON string for an S3-compatible link (MinIO or Cleura). + /// Pass null to remove the policy. /// public async Task SetBucketPolicyAsync( Guid tenantId, Guid linkId, string? policyJson, CancellationToken ct = default) { - StorageLink link = await GetCleuraLinkOrThrowAsync(tenantId, linkId, ct); + StorageLink link = await GetS3LinkOrThrowAsync(tenantId, linkId, ct); (string accessKey, string secretKey) = await GetStoredCredentialsAsync(tenantId, linkId, ct); + string? kubeconfig = await ResolveMinioKubeconfigAsync(link, ct); + + if (kubeconfig is not null && link.Endpoint is not null) + { + using ProxiedS3Client proxied = await KubernetesS3Proxy.OpenAsync(kubeconfig, link.Endpoint, accessKey, secretKey, ct: ct) + ?? throw new InvalidOperationException($"Cannot parse internal MinIO endpoint: {link.Endpoint}"); + await openStackS3.SetBucketPolicyAsync(proxied.S3, link.BucketName!, policyJson, ct); + return; + } await openStackS3.SetBucketPolicyAsync( link.Endpoint!, accessKey, secretKey, link.BucketName!, link.Region!, policyJson, ct); @@ -552,6 +741,73 @@ public class StorageService(IDbContextFactory dbFactory, V ?? throw new InvalidOperationException("Cleura S3 storage link not found."); } + /// + /// Loads any S3-compatible StorageLink (MinIO or CleuraS3) or throws if not found. + /// For MinIO links, Component and its Cluster are included so the kubeconfig is available. + /// + private async Task GetS3LinkOrThrowAsync( + Guid tenantId, Guid linkId, CancellationToken ct) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.StorageLinks + .Include(l => l.Component).ThenInclude(c => c!.Cluster) + .FirstOrDefaultAsync(l => l.Id == linkId && l.TenantId == tenantId + && (l.Provider == StorageProvider.CleuraS3 || l.Provider == StorageProvider.MinIO), ct) + ?? throw new InvalidOperationException("S3-compatible storage link not found."); + } + + /// + /// Returns the kubeconfig for the cluster that hosts a MinIO StorageLink. + /// First checks the eagerly-loaded Component → Cluster navigation (set for links + /// registered after ComponentId tracking was added). Falls back to a DB query + /// for older links where ComponentId is null, matching by tenant + MinIO component + /// namespace derived from the stored internal endpoint. + /// Returns null for non-MinIO links. + /// + private async Task ResolveMinioKubeconfigAsync(StorageLink link, CancellationToken ct) + { + if (link.Provider != StorageProvider.MinIO) + return null; + + // Fast path: ComponentId was set at registration time. + string? kubeconfig = link.Component?.Cluster?.Kubeconfig; + if (!string.IsNullOrWhiteSpace(kubeconfig)) + return kubeconfig; + + // Fallback for links registered before ComponentId tracking. + // Parse the namespace from the internal endpoint and find a matching cluster. + var parsed = KubernetesS3Proxy.ParseInternalServiceUrl(link.Endpoint ?? ""); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + IQueryable query = db.ClusterComponents + .Include(c => c.Cluster) + .Where(c => c.Cluster.TenantId == link.TenantId + && (c.Name == "minio-operator" || c.Name == "minio") + && c.Status == ComponentStatus.Installed + && c.Cluster.Kubeconfig != null); + + // Prefer the component whose namespace matches the endpoint namespace. + if (parsed is not null) + query = query.OrderByDescending(c => c.Namespace == parsed.Value.Namespace); + + ClusterComponent? component = await query.FirstOrDefaultAsync(ct); + return component?.Cluster?.Kubeconfig; + } + + /// + /// Returns the stored S3 credentials for a storage link. Returns empty strings when not found. + /// Used by other services (e.g. HarborService) to read credentials without a vault error. + /// + public async Task<(string AccessKey, string SecretKey)> GetStoredCredentialsInternalAsync( + Guid tenantId, Guid linkId, CancellationToken ct = default) + { + string? accessKey = await vaultService.GetStorageLinkSecretValueAsync(tenantId, linkId, "ACCESS_KEY", ct); + string? secretKey = await vaultService.GetStorageLinkSecretValueAsync(tenantId, linkId, "SECRET_KEY", ct); + return (accessKey ?? "", secretKey ?? ""); + } + /// /// Retrieves the stored S3 credentials (access/secret key) from vault for a storage link. /// diff --git a/src/EntKube.Web/Services/TenantService.cs b/src/EntKube.Web/Services/TenantService.cs index c39ff5e..659741c 100644 --- a/src/EntKube.Web/Services/TenantService.cs +++ b/src/EntKube.Web/Services/TenantService.cs @@ -294,6 +294,18 @@ public class TenantService(IDbContextFactory dbFactory) return true; } + /// Returns all apps across all customers of a tenant, each with its Customer included. + public async Task> GetAppsWithCustomersAsync(Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Apps + .Include(a => a.Customer) + .Where(a => a.Customer.TenantId == tenantId) + .OrderBy(a => a.Customer.Name).ThenBy(a => a.Name) + .ToListAsync(ct); + } + // --- Kubernetes Cluster CRUD --- public async Task> GetClustersAsync(Guid tenantId, CancellationToken ct = default) diff --git a/src/EntKube.Web/Services/UptimeTrackingService.cs b/src/EntKube.Web/Services/UptimeTrackingService.cs new file mode 100644 index 0000000..094d112 --- /dev/null +++ b/src/EntKube.Web/Services/UptimeTrackingService.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore; +using EntKube.Web.Data; + +namespace EntKube.Web.Services; + +public class UptimeTrackingService( + IServiceScopeFactory scopeFactory, + ILogger logger) : BackgroundService +{ + private static readonly TimeSpan Interval = TimeSpan.FromMinutes(5); + private static readonly TimeSpan StartupDelay = TimeSpan.FromSeconds(60); + private static readonly TimeSpan RetentionPeriod = TimeSpan.FromDays(90); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(StartupDelay, stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + await TakeSnapshotsAsync(stoppingToken); + await Task.Delay(Interval, stoppingToken); + } + } + + private async Task TakeSnapshotsAsync(CancellationToken ct) + { + try + { + using IServiceScope scope = scopeFactory.CreateScope(); + var dbFactory = scope.ServiceProvider.GetRequiredService>(); + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + DateTime cutoff = DateTime.UtcNow - RetentionPeriod; + DateTime now = DateTime.UtcNow; + + // Prune old snapshots and route history to control table growth + int deleted = await db.DeploymentHealthSnapshots + .Where(s => s.SnapshotAt < cutoff) + .ExecuteDeleteAsync(ct); + + int routeHistDeleted = await db.ExternalRouteHealthHistories + .Where(h => h.CheckedAt < cutoff) + .ExecuteDeleteAsync(ct); + + if (deleted > 0 || routeHistDeleted > 0) + logger.LogDebug("Pruned {Snaps} health snapshots and {Routes} route history entries older than {Days} days", + deleted, routeHistDeleted, (int)RetentionPeriod.TotalDays); + + // Read current deployment health state from DB (no K8s call needed) + List<(Guid Id, HealthStatus Health, SyncStatus Sync)> deployments = await db.AppDeployments + .Select(d => new ValueTuple(d.Id, d.HealthStatus, d.SyncStatus)) + .ToListAsync(ct); + + foreach ((Guid id, HealthStatus health, SyncStatus sync) in deployments) + { + db.DeploymentHealthSnapshots.Add(new DeploymentHealthSnapshot + { + DeploymentId = id, + HealthStatus = health, + SyncStatus = sync, + SnapshotAt = now + }); + } + + await db.SaveChangesAsync(ct); + logger.LogDebug("Recorded {Count} health snapshots at {Time:u}", deployments.Count, now); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Uptime tracking snapshot failed"); + } + } +} diff --git a/src/EntKube.Web/Services/UserAccessService.cs b/src/EntKube.Web/Services/UserAccessService.cs new file mode 100644 index 0000000..fb4da1a --- /dev/null +++ b/src/EntKube.Web/Services/UserAccessService.cs @@ -0,0 +1,52 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; + +namespace EntKube.Web.Services; + +public class UserAccessService(IDbContextFactory dbFactory) +{ + public async Task IsGlobalAdminAsync(string userId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await ( + from ur in db.UserRoles + join r in db.Roles on ur.RoleId equals r.Id + where ur.UserId == userId && r.Name == "Admin" + select ur + ).AnyAsync(ct); + } + + public async Task HasAnyTenantMembershipAsync(string userId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.TenantMemberships.AnyAsync(m => m.UserId == userId, ct); + } + + public async Task HasTenantAccessAsync(string userId, Guid tenantId, CancellationToken ct = default) + { + if (await IsGlobalAdminAsync(userId, ct)) return true; + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.TenantMemberships.AnyAsync(m => m.UserId == userId && m.TenantId == tenantId, ct); + } + + public async Task HasAnyAccessAsync(string userId, CancellationToken ct = default) + { + if (await IsGlobalAdminAsync(userId, ct)) return true; + return await HasAnyTenantMembershipAsync(userId, ct); + } + + public async Task> GetAccessibleTenantsAsync(string userId, CancellationToken ct = default) + { + bool isAdmin = await IsGlobalAdminAsync(userId, ct); + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + if (isAdmin) + return await db.Tenants.OrderBy(t => t.Name).ToListAsync(ct); + + return await db.TenantMemberships + .Where(m => m.UserId == userId) + .Select(m => m.Tenant) + .OrderBy(t => t.Name) + .ToListAsync(ct); + } +} diff --git a/src/EntKube.Web/Services/VaultService.cs b/src/EntKube.Web/Services/VaultService.cs index 9de6cf7..4d4169e 100644 --- a/src/EntKube.Web/Services/VaultService.cs +++ b/src/EntKube.Web/Services/VaultService.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using System.Text; using EntKube.Web.Data; using Microsoft.EntityFrameworkCore; @@ -451,6 +453,17 @@ public class VaultService(IDbContextFactory dbFactory, Vau .ToListAsync(ct); } + public async Task> GetMongoDatabaseSecretsAsync( + Guid tenantId, Guid mongoDatabaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.MongoDatabaseId == mongoDatabaseId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + /// /// Returns the decrypted password for a CNPG database. Used when another /// service (e.g. Keycloak) needs to read the DB password to propagate it. @@ -471,6 +484,170 @@ public class VaultService(IDbContextFactory dbFactory, Vau return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); } + /// + /// Stores or updates a CNPG-database-scoped secret that is NOT synced to Kubernetes. + /// Used for administrative credentials (e.g. superuser password) that should live + /// in the vault for reference but must never be pushed to a K8s Secret. + /// + public async Task SetCnpgDatabaseAdminSecretAsync( + Guid tenantId, Guid cnpgDatabaseId, string name, string value, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.CnpgDatabaseId == cnpgDatabaseId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.SyncToKubernetes = false; + existing.KubernetesSecretName = null; + existing.KubernetesNamespace = null; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + CnpgDatabaseId = cnpgDatabaseId, + SyncToKubernetes = false + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + /// + /// Decrypts and returns a CNPG-database-scoped admin secret value by name. + /// + public async Task GetCnpgDatabaseAdminSecretValueAsync( + Guid tenantId, Guid cnpgDatabaseId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.CnpgDatabaseId == cnpgDatabaseId + && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + public async Task SetCnpgClusterSecretAsync( + Guid tenantId, Guid cnpgClusterId, string name, string value, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.CnpgClusterId == cnpgClusterId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + CnpgClusterId = cnpgClusterId, + SyncToKubernetes = false + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + public async Task GetCnpgClusterSecretValueAsync( + Guid tenantId, Guid cnpgClusterId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.CnpgClusterId == cnpgClusterId + && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + public async Task> GetCnpgClusterSecretsAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.CnpgClusterId == cnpgClusterId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + + /// + /// Returns all vault secrets belonging to a CNPG cluster: cluster-level secrets + /// (e.g. superuser credentials) plus secrets for every database in the cluster. + /// Database secrets include the CnpgDatabase navigation property so callers can + /// group or label them by database name. + /// + public async Task> GetAllCnpgSecretsForClusterAsync( + Guid tenantId, Guid cnpgClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List clusterSecrets = await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.CnpgClusterId == cnpgClusterId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + + List databaseSecrets = await db.Set() + .Include(s => s.CnpgDatabase) + .Where(s => s.Vault.TenantId == tenantId + && s.CnpgDatabase != null + && s.CnpgDatabase.CnpgClusterId == cnpgClusterId) + .OrderBy(s => s.CnpgDatabase!.Name) + .ThenBy(s => s.Name) + .ToListAsync(ct); + + return [.. clusterSecrets, .. databaseSecrets]; + } + /// /// Creates or updates a vault secret scoped to a MongoDB database. /// Mirrors SetCnpgDatabaseSecretAsync but uses MongoDatabaseId. @@ -589,6 +766,158 @@ public class VaultService(IDbContextFactory dbFactory, Vau return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); } + // --- Registered Postgres Database Secrets --- + + /// + /// Creates or updates a connection credential secret scoped to a registered + /// (non-CNPG) PostgreSQL database, pre-configured for Kubernetes sync. + /// + public async Task SetRegisteredPostgresDatabaseSecretAsync( + Guid tenantId, Guid registeredPostgresDatabaseId, string name, string value, + string k8sSecretName, string k8sNamespace, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RegisteredPostgresDatabaseId == registeredPostgresDatabaseId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.SyncToKubernetes = true; + existing.KubernetesSecretName = k8sSecretName; + existing.KubernetesNamespace = k8sNamespace; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + RegisteredPostgresDatabaseId = registeredPostgresDatabaseId, + SyncToKubernetes = true, + KubernetesSecretName = k8sSecretName, + KubernetesNamespace = k8sNamespace + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + /// + /// Returns all secrets scoped to a registered Postgres database. + /// + public async Task> GetRegisteredPostgresDatabaseSecretsAsync( + Guid tenantId, Guid registeredPostgresDatabaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId + && s.RegisteredPostgresDatabaseId == registeredPostgresDatabaseId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + + /// + /// Returns the decrypted password for a registered Postgres database. + /// + public async Task GetRegisteredPostgresDatabasePasswordAsync( + Guid tenantId, Guid registeredPostgresDatabaseId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RegisteredPostgresDatabaseId == registeredPostgresDatabaseId + && s.Name == "PASSWORD", ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + /// + /// Creates or updates the admin password secret for a RegisteredPostgresInstance. + /// Stored as a named app-independent vault secret using a unique key per instance. + /// + public async Task SetRegisteredPostgresAdminPasswordAsync( + Guid tenantId, Guid instanceId, string password, CancellationToken ct = default) + { + await InitializeVaultAsync(tenantId, ct); + + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + string secretName = AdminPasswordSecretName(instanceId); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId && s.Name == secretName, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, password); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + db.Set().Add(new VaultSecret + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = secretName, + EncryptedValue = ciphertext, + Nonce = nonce + }); + + await db.SaveChangesAsync(ct); + } + + /// + /// Retrieves the decrypted admin password for a RegisteredPostgresInstance. + /// Returns null if not yet set. + /// + public async Task GetRegisteredPostgresAdminPasswordAsync( + Guid tenantId, Guid instanceId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + string secretName = AdminPasswordSecretName(instanceId); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId && s.Name == secretName, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + private static string AdminPasswordSecretName(Guid instanceId) => + $"__reg-pg-{instanceId:N}__admin"; + // --- Secret Management --- /// @@ -694,10 +1023,13 @@ public class VaultService(IDbContextFactory dbFactory, Vau /// /// Configures Kubernetes sync for a secret — enabling or disabling sync - /// and setting the target Secret name and namespace. + /// and setting the target Secret name, namespace, and cluster. + /// Pass for app-scoped secrets where the cluster + /// cannot be derived from the secret's owner relationship. /// public async Task ConfigureKubernetesSyncAsync( - Guid secretId, bool syncEnabled, string? secretName, string? ns, CancellationToken ct = default) + Guid secretId, bool syncEnabled, string? secretName, string? ns, + CancellationToken ct = default, Guid? clusterId = null) { using ApplicationDbContext db = dbFactory.CreateDbContext(); @@ -711,10 +1043,286 @@ public class VaultService(IDbContextFactory dbFactory, Vau secret.SyncToKubernetes = syncEnabled; secret.KubernetesSecretName = secretName; secret.KubernetesNamespace = ns; + if (clusterId.HasValue) + { + secret.KubernetesClusterId = clusterId; + } + else if (!syncEnabled) + { + secret.KubernetesClusterId = null; + } secret.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(ct); } + /// + /// Syncs all app-scoped secrets that have + /// enabled to their target Kubernetes cluster. Secrets are grouped by + /// (KubernetesSecretName, KubernetesNamespace) and written as Opaque Secrets. + /// Secrets without a are reported + /// in the output as needing cluster configuration but do not block the rest. + /// + public async Task SyncAppSecretsToKubernetesAsync( + Guid tenantId, Guid appId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + // Load ALL sync-enabled secrets — including those missing a cluster reference, + // so we can report them clearly instead of silently skipping. + List allSecrets = await db.Set() + .Include(s => s.KubernetesCluster) + .Where(s => s.AppId == appId && s.SyncToKubernetes) + .ToListAsync(ct); + + if (allSecrets.Count == 0) + { + return new HelmExecutionResult + { + Success = true, + Output = "No app secrets marked for Kubernetes sync." + }; + } + + List results = []; + + // Secrets without a K8s Secret name or cluster are not actionable — tell the user. + foreach (VaultSecret s in allSecrets.Where(s => string.IsNullOrWhiteSpace(s.KubernetesSecretName))) + { + results.Add($"⚠ '{s.Name}' has no K8s Secret name configured — open K8s sync settings to assign one."); + } + foreach (VaultSecret s in allSecrets.Where(s => !string.IsNullOrWhiteSpace(s.KubernetesSecretName) && s.KubernetesClusterId == null)) + { + results.Add($"⚠ '{s.Name}' → '{s.KubernetesSecretName}' has no target cluster — open K8s sync settings to assign one."); + } + + // Only process secrets that are fully configured. + List actionable = allSecrets + .Where(s => s.KubernetesClusterId != null && !string.IsNullOrWhiteSpace(s.KubernetesSecretName)) + .ToList(); + + if (actionable.Count == 0) + { + return new HelmExecutionResult + { + Success = results.Count == 0, + Output = results.Count > 0 ? string.Join("\n", results) : "No app secrets with both a K8s Secret name and a target cluster." + }; + } + + // Group by cluster so we only write one kubeconfig temp file per cluster. + IEnumerable> clusterGroups = + actionable.GroupBy(s => s.KubernetesClusterId!.Value); + + foreach (IGrouping clusterGroup in clusterGroups) + { + KubernetesCluster? cluster = clusterGroup.First().KubernetesCluster; + if (cluster is null || string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + { + results.Add($"✗ Cluster '{clusterGroup.Key}' has no kubeconfig — skipping {clusterGroup.Count()} secret(s)."); + continue; + } + + string tempKubeconfig = Path.Combine(Path.GetTempPath(), $"entkube-{Guid.NewGuid()}.kubeconfig"); + + try + { + await File.WriteAllTextAsync(tempKubeconfig, cluster.Kubeconfig, ct); + + // Within this cluster, group secrets by (K8sSecretName, Namespace). + IEnumerable> secretGroups = + clusterGroup.GroupBy(s => ( + SecretName: s.KubernetesSecretName!, + Namespace: s.KubernetesNamespace ?? "default" + )); + + foreach (IGrouping<(string SecretName, string Namespace), VaultSecret> group in secretGroups) + { + string k8sSecretName = group.Key.SecretName; + string ns = group.Key.Namespace; + + // Ensure namespace exists. + await RunProcessAsync("kubectl", + $"create namespace {ns} --kubeconfig {tempKubeconfig}", ct); + + // Decrypt each secret value. + List literals = []; + + foreach (VaultSecret vaultSecret in group) + { + string? plainValue = await GetAppSecretValueAsync(tenantId, appId, vaultSecret.Name, ct); + + if (plainValue is not null) + { + // Shell-safe: write value to a temp file so we avoid quoting issues. + string tmpVal = Path.Combine(Path.GetTempPath(), $"entkube-val-{Guid.NewGuid()}"); + await File.WriteAllTextAsync(tmpVal, plainValue, ct); + literals.Add($"--from-file={vaultSecret.Name}={tmpVal}"); + } + } + + if (literals.Count == 0) + { + results.Add($" (skipped '{k8sSecretName}/{ns}' — no decryptable values)"); + continue; + } + + // Delete and recreate for clean state. + await RunProcessAsync("kubectl", + $"delete secret {k8sSecretName} --namespace {ns} --ignore-not-found --kubeconfig {tempKubeconfig}", ct); + + HelmExecutionResult createResult = await RunProcessAsync("kubectl", + $"create secret generic {k8sSecretName} --namespace {ns} {string.Join(" ", literals)} --kubeconfig {tempKubeconfig}", ct); + + // Clean up temp value files. + foreach (string lit in literals) + { + // Extract file path from "--from-file=KEY=PATH" + int eq2 = lit.IndexOf('=', lit.IndexOf('=') + 1); + if (eq2 >= 0) + { + string tmpFile = lit[(eq2 + 1)..]; + if (File.Exists(tmpFile)) File.Delete(tmpFile); + } + } + + if (createResult.Success) + { + results.Add($"✓ Secret '{k8sSecretName}' synced to '{ns}' on '{cluster.Name}' ({group.Count()} keys)"); + } + else + { + results.Add($"✗ Secret '{k8sSecretName}' failed on '{cluster.Name}': {createResult.Output}"); + } + } + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + } + } + + bool allSucceeded = results.All(r => r.StartsWith("✓")); + return new HelmExecutionResult + { + Success = allSucceeded, + Output = string.Join("\n", results) + }; + } + + // --- Git Repository Secrets --- + + /// + /// Stores or updates a secret scoped to a Git repository credential. + /// Conventional key names: "PAT" (HttpsPat auth), "PASSWORD" (HttpsPassword auth), + /// "SSH_PRIVATE_KEY" (SshKey auth). + /// + public async Task SetGitRepositorySecretAsync( + Guid tenantId, Guid gitRepositoryId, string name, string value, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.GitRepositoryId == gitRepositoryId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + GitRepositoryId = gitRepositoryId + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + /// + /// Decrypts and returns a specific git repository secret value. + /// Returns null if not found. + /// + public async Task GetGitRepositorySecretValueAsync( + Guid tenantId, Guid gitRepositoryId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.GitRepositoryId == gitRepositoryId + && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + /// + /// Lists all vault secrets scoped to a git repository (metadata only). + /// + public async Task> GetGitRepositorySecretsAsync( + Guid tenantId, Guid gitRepositoryId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.GitRepositoryId == gitRepositoryId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + + // --- Private Helpers --- + + private static async Task RunProcessAsync( + string executable, string arguments, CancellationToken ct = default) + { + ProcessStartInfo psi = new(executable, arguments) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using Process process = new() { StartInfo = psi }; + StringBuilder output = new(); + + process.OutputDataReceived += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); }; + process.ErrorDataReceived += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await process.WaitForExitAsync(ct); + + return new HelmExecutionResult + { + Success = process.ExitCode == 0, + ExitCode = process.ExitCode, + Output = output.ToString().TrimEnd() + }; + } + // --- Cluster Components --- /// @@ -770,12 +1378,219 @@ public class VaultService(IDbContextFactory dbFactory, Vau return true; } - // --- Private Helpers --- - /// - /// Unseals the tenant's vault and returns the decrypted DEK. - /// This is the "auto-unseal" — we just decrypt using the root key from config. - /// + // --- RabbitMQ Cluster Secrets --- + + public async Task SetRabbitMQClusterSecretAsync( + Guid tenantId, Guid rabbitMQClusterId, string name, string value, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RabbitMQClusterId == rabbitMQClusterId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + RabbitMQClusterId = rabbitMQClusterId, + SyncToKubernetes = false + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + public async Task GetRabbitMQClusterSecretValueAsync( + Guid tenantId, Guid rabbitMQClusterId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RabbitMQClusterId == rabbitMQClusterId + && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + public async Task> GetRabbitMQClusterSecretsAsync( + Guid tenantId, Guid rabbitMQClusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.RabbitMQClusterId == rabbitMQClusterId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + + public async Task DeleteRabbitMQClusterSecretAsync( + Guid tenantId, Guid rabbitMQClusterId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RabbitMQClusterId == rabbitMQClusterId + && s.Name == name, ct); + + if (secret is not null) + { + db.Set().Remove(secret); + await db.SaveChangesAsync(ct); + } + } + + // ── Redis cluster secrets ──────────────────────────────────────────────────── + + public async Task SetRedisClusterSecretAsync( + Guid tenantId, Guid clusterId, string name, string value, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RedisClusterId == clusterId + && s.Name == name, ct); + + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + RedisClusterId = clusterId, + SyncToKubernetes = false + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + public async Task GetRedisClusterSecretValueAsync( + Guid tenantId, Guid clusterId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.Vault.TenantId == tenantId + && s.RedisClusterId == clusterId + && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + public async Task> GetRedisClusterSecretsAsync( + Guid tenantId, Guid clusterId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + return await db.Set() + .Where(s => s.Vault.TenantId == tenantId && s.RedisClusterId == clusterId) + .OrderBy(s => s.Name) + .ToListAsync(ct); + } + + // ── VPN remote endpoint secrets ────────────────────────────────────────────── + + public async Task SetVpnRemoteEndpointSecretAsync( + Guid tenantId, Guid endpointId, string name, string value, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + (byte[] ciphertext, byte[] nonce) = encryption.Encrypt(dataKey, value); + + VaultSecret? existing = await db.Set() + .FirstOrDefaultAsync(s => s.VpnRemoteEndpointId == endpointId && s.Name == name, ct); + + if (existing is not null) + { + existing.EncryptedValue = ciphertext; + existing.Nonce = nonce; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + return existing; + } + + SecretVault vault = (await GetVaultAsync(tenantId, ct))!; + + VaultSecret secret = new() + { + Id = Guid.NewGuid(), + VaultId = vault.Id, + Name = name, + EncryptedValue = ciphertext, + Nonce = nonce, + VpnRemoteEndpointId = endpointId + }; + + db.Set().Add(secret); + await db.SaveChangesAsync(ct); + return secret; + } + + public async Task GetVpnRemoteEndpointSecretValueAsync( + Guid tenantId, Guid endpointId, string name, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VaultSecret? secret = await db.Set() + .FirstOrDefaultAsync(s => s.VpnRemoteEndpointId == endpointId && s.Name == name, ct); + + if (secret is null) return null; + + byte[] dataKey = await UnsealVaultAsync(tenantId, ct); + return encryption.Decrypt(dataKey, secret.EncryptedValue, secret.Nonce); + } + + /// Unseals the tenant's vault and returns the decrypted DEK. private async Task UnsealVaultAsync(Guid tenantId, CancellationToken ct) { using ApplicationDbContext db = dbFactory.CreateDbContext(); diff --git a/src/EntKube.Web/Services/VpnService.cs b/src/EntKube.Web/Services/VpnService.cs new file mode 100644 index 0000000..547c696 --- /dev/null +++ b/src/EntKube.Web/Services/VpnService.cs @@ -0,0 +1,616 @@ +using EntKube.Web.Data; +using Microsoft.EntityFrameworkCore; +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace EntKube.Web.Services; + +/// +/// Manages VPN tunnels: data lifecycle, swanctl config generation, K8s config sync, +/// and connection status polling. Two tunnel types are supported: +/// +/// SiteToSite — StrongSwan gateway pod on one or more platform clusters, +/// connecting to external (customer) sites via IKEv2/IPsec. +/// +/// ClusterMesh — Submariner broker + gateways across multiple platform clusters, +/// enabling encrypted pod-to-pod and service cross-cluster connectivity. +/// +public class VpnService( + IDbContextFactory dbFactory, + VaultService vaultService, + ComponentLifecycleService lifecycleService) +{ + // ── Tunnel CRUD ───────────────────────────────────────────────────────────── + + public async Task CreateTunnelAsync( + Guid tenantId, string name, VpnTunnelType type, string? description = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VpnTunnel tunnel = new() + { + Id = Guid.NewGuid(), + TenantId = tenantId, + Name = name, + Description = description, + TunnelType = type, + Status = VpnTunnelStatus.Draft + }; + + db.VpnTunnels.Add(tunnel); + await db.SaveChangesAsync(ct); + return tunnel; + } + + public async Task GetTunnelWithDetailsAsync(Guid tunnelId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.VpnTunnels + .Include(t => t.LocalEndpoints).ThenInclude(e => e.Cluster) + .Include(t => t.LocalEndpoints).ThenInclude(e => e.Component) + .Include(t => t.RemoteEndpoints) + .FirstOrDefaultAsync(t => t.Id == tunnelId, ct); + } + + public async Task> GetTunnelsForTenantAsync(Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + return await db.VpnTunnels + .Where(t => t.TenantId == tenantId) + .Include(t => t.LocalEndpoints).ThenInclude(e => e.Cluster) + .Include(t => t.RemoteEndpoints) + .OrderBy(t => t.CreatedAt) + .ToListAsync(ct); + } + + public async Task UpdateCryptoSettingsAsync( + Guid tunnelId, int ikeVersion, string ikeProposal, string espProposal, + int dpdDelay, int dpdTimeout, int ikeLifetime = 86400, int childLifetime = 3600, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnTunnel tunnel = await db.VpnTunnels.FindAsync([tunnelId], ct) + ?? throw new InvalidOperationException($"Tunnel {tunnelId} not found."); + + tunnel.IkeVersion = ikeVersion; + tunnel.IkeProposal = ikeProposal; + tunnel.EspProposal = espProposal; + tunnel.DpdDelay = dpdDelay; + tunnel.DpdTimeout = dpdTimeout; + tunnel.IkeLifetime = ikeLifetime; + tunnel.ChildLifetime = childLifetime; + await db.SaveChangesAsync(ct); + } + + public async Task UpdateTunnelStatusAsync(Guid tunnelId, VpnTunnelStatus status, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnTunnel tunnel = await db.VpnTunnels.FindAsync([tunnelId], ct) + ?? throw new InvalidOperationException($"Tunnel {tunnelId} not found."); + tunnel.Status = status; + await db.SaveChangesAsync(ct); + } + + public async Task DeleteTunnelAsync(Guid tunnelId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnTunnel tunnel = await db.VpnTunnels.FindAsync([tunnelId], ct) + ?? throw new InvalidOperationException($"Tunnel {tunnelId} not found."); + db.VpnTunnels.Remove(tunnel); + await db.SaveChangesAsync(ct); + } + + // ── Local endpoints ────────────────────────────────────────────────────────── + + public async Task AddLocalEndpointAsync( + Guid tunnelId, Guid clusterId, string[] subnets, VpnEndpointRole role, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnLocalEndpoint ep = new() + { + Id = Guid.NewGuid(), + VpnTunnelId = tunnelId, + ClusterId = clusterId, + Subnets = JsonSerializer.Serialize(subnets), + Role = role, + Status = VpnEndpointStatus.Pending + }; + db.VpnLocalEndpoints.Add(ep); + await db.SaveChangesAsync(ct); + return ep; + } + + public async Task UpdateLocalEndpointAsync( + Guid endpointId, string[] subnets, string? publicIp, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnLocalEndpoint ep = await db.VpnLocalEndpoints.FindAsync([endpointId], ct) + ?? throw new InvalidOperationException($"Local endpoint {endpointId} not found."); + ep.Subnets = JsonSerializer.Serialize(subnets); + if (publicIp is not null) ep.PublicIp = publicIp; + await db.SaveChangesAsync(ct); + } + + public async Task LinkComponentAsync(Guid endpointId, Guid componentId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnLocalEndpoint ep = await db.VpnLocalEndpoints.FindAsync([endpointId], ct) + ?? throw new InvalidOperationException($"Local endpoint {endpointId} not found."); + ep.ComponentId = componentId; + ep.Status = VpnEndpointStatus.Ready; + await db.SaveChangesAsync(ct); + } + + public async Task RemoveLocalEndpointAsync(Guid endpointId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnLocalEndpoint ep = await db.VpnLocalEndpoints.FindAsync([endpointId], ct) + ?? throw new InvalidOperationException($"Local endpoint {endpointId} not found."); + db.VpnLocalEndpoints.Remove(ep); + await db.SaveChangesAsync(ct); + } + + // ── Remote endpoints (SiteToSite only) ────────────────────────────────────── + + public async Task AddRemoteEndpointAsync( + Guid tunnelId, string name, string publicIp, string[] subnets, + VpnAuthMode authMode, string psk, Guid tenantId, + string? localId = null, string? remoteId = null, + CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VpnRemoteEndpoint ep = new() + { + Id = Guid.NewGuid(), + VpnTunnelId = tunnelId, + Name = name, + PublicIp = publicIp, + Subnets = JsonSerializer.Serialize(subnets), + AuthMode = authMode, + LocalId = localId, + RemoteId = remoteId, + Status = VpnConnectionStatus.Unknown + }; + + db.VpnRemoteEndpoints.Add(ep); + await db.SaveChangesAsync(ct); + + await vaultService.SetVpnRemoteEndpointSecretAsync(tenantId, ep.Id, "PSK", psk, ct); + + return ep; + } + + public async Task UpdateRemoteEndpointAsync( + Guid endpointId, string publicIp, string[] subnets, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnRemoteEndpoint ep = await db.VpnRemoteEndpoints.FindAsync([endpointId], ct) + ?? throw new InvalidOperationException($"Remote endpoint {endpointId} not found."); + ep.PublicIp = publicIp; + ep.Subnets = JsonSerializer.Serialize(subnets); + await db.SaveChangesAsync(ct); + } + + public async Task RotatePskAsync(Guid endpointId, string newPsk, Guid tenantId, CancellationToken ct = default) + { + await vaultService.SetVpnRemoteEndpointSecretAsync(tenantId, endpointId, "PSK", newPsk, ct); + } + + public async Task RemoveRemoteEndpointAsync(Guid endpointId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + VpnRemoteEndpoint ep = await db.VpnRemoteEndpoints.FindAsync([endpointId], ct) + ?? throw new InvalidOperationException($"Remote endpoint {endpointId} not found."); + db.VpnRemoteEndpoints.Remove(ep); + await db.SaveChangesAsync(ct); + } + + // ── Config generation ──────────────────────────────────────────────────────── + + /// + /// Generates a swanctl.conf for the given tunnel, containing one connection + /// block per remote endpoint. + /// + public async Task GenerateSwanctlConfigAsync(Guid tunnelId, CancellationToken ct = default) + { + VpnTunnel tunnel = await GetTunnelWithDetailsAsync(tunnelId, ct) + ?? throw new InvalidOperationException($"Tunnel {tunnelId} not found."); + + StringBuilder sb = new(); + sb.AppendLine("connections {"); + + VpnLocalEndpoint? gateway = tunnel.LocalEndpoints.FirstOrDefault(e => e.Role == VpnEndpointRole.Gateway); + string localIp = gateway?.PublicIp ?? "%any"; + string[] localSubnets = gateway is not null + ? (JsonSerializer.Deserialize(gateway.Subnets) ?? []) + : []; + + foreach (VpnRemoteEndpoint remote in tunnel.RemoteEndpoints) + { + string connName = SanitizeConnectionName(remote.Name); + string[] remoteSubnets = JsonSerializer.Deserialize(remote.Subnets) ?? []; + + string localId = !string.IsNullOrWhiteSpace(remote.LocalId) ? remote.LocalId : localIp; + string remoteId = !string.IsNullOrWhiteSpace(remote.RemoteId) ? remote.RemoteId : remote.PublicIp; + + sb.AppendLine($" {connName} {{"); + sb.AppendLine($" version = {tunnel.IkeVersion}"); + sb.AppendLine($" proposals = {tunnel.IkeProposal}"); + sb.AppendLine($" rekey_time = {tunnel.IkeLifetime}s"); + sb.AppendLine($" dpd_delay = {tunnel.DpdDelay}s"); + sb.AppendLine($" dpd_timeout = {tunnel.DpdTimeout}s"); + sb.AppendLine($" local_addrs = {localIp}"); + sb.AppendLine($" remote_addrs = {remote.PublicIp}"); + sb.AppendLine($" local {{"); + sb.AppendLine($" auth = psk"); + sb.AppendLine($" id = {localId}"); + sb.AppendLine($" subnets = {string.Join(",", localSubnets)}"); + sb.AppendLine($" }}"); + sb.AppendLine($" remote {{"); + sb.AppendLine($" auth = psk"); + sb.AppendLine($" id = {remoteId}"); + sb.AppendLine($" subnets = {string.Join(",", remoteSubnets)}"); + sb.AppendLine($" }}"); + sb.AppendLine($" children {{"); + sb.AppendLine($" {connName} {{"); + sb.AppendLine($" esp_proposals = {tunnel.EspProposal}"); + sb.AppendLine($" rekey_time = {tunnel.ChildLifetime}s"); + sb.AppendLine($" start_action = trap"); + sb.AppendLine($" dpd_action = restart"); + sb.AppendLine($" }}"); + sb.AppendLine($" }}"); + sb.AppendLine($" }}"); + } + + sb.AppendLine("}"); + return sb.ToString(); + } + + /// + /// Generates the swanctl secrets section (PSKs) for the tunnel. + /// PSK values are decrypted from the vault at call time. + /// + public async Task GenerateSwanctlSecretsAsync(Guid tunnelId, Guid tenantId, CancellationToken ct = default) + { + VpnTunnel tunnel = await GetTunnelWithDetailsAsync(tunnelId, ct) + ?? throw new InvalidOperationException($"Tunnel {tunnelId} not found."); + + StringBuilder sb = new(); + sb.AppendLine("secrets {"); + + foreach (VpnRemoteEndpoint remote in tunnel.RemoteEndpoints) + { + string? psk = await vaultService.GetVpnRemoteEndpointSecretValueAsync(tenantId, remote.Id, "PSK", ct); + if (psk is null) continue; + + sb.AppendLine($" ike-{SanitizeConnectionName(remote.Name)} {{"); + sb.AppendLine($" id = {remote.PublicIp}"); + sb.AppendLine($" secret = \"{psk}\""); + sb.AppendLine($" }}"); + } + + sb.AppendLine("}"); + return sb.ToString(); + } + + /// + /// Applies the swanctl config and secrets as a ConfigMap and opaque Secret on the + /// cluster associated with the given local endpoint, using kubectl apply. + /// + public async Task SyncConfigToClusterAsync( + Guid localEndpointId, Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + VpnLocalEndpoint ep = await db.VpnLocalEndpoints + .Include(e => e.Cluster) + .FirstOrDefaultAsync(e => e.Id == localEndpointId, ct) + ?? throw new InvalidOperationException($"Local endpoint {localEndpointId} not found."); + + string config = await GenerateSwanctlConfigAsync(ep.VpnTunnelId, ct); + string secrets = await GenerateSwanctlSecretsAsync(ep.VpnTunnelId, tenantId, ct); + + string secretsB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(secrets)); + + string manifest = BuildSyncManifest(config, secretsB64); + + return await lifecycleService.ApplyRawManifestAsync(ep.Cluster, manifest, delete: false, ct); + } + + // ── CIDR auto-detection ────────────────────────────────────────────────────── + + /// + /// Tries to detect the pod and service CIDRs for a cluster by querying its + /// Kubernetes API. Uses kubeadm-config and node specs as sources. + /// Returns null values for either CIDR if detection fails — the operator + /// can always fill them in manually. + /// + public async Task<(string? PodCidr, string? ServiceCidr)> DetectClusterCidrsAsync( + KubernetesCluster cluster, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(cluster.Kubeconfig)) + return (null, null); + + string tempKubeconfig = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(tempKubeconfig, cluster.Kubeconfig, ct); + + string? podCidr = null; + string? serviceCidr = null; + + // ── Source 1: kube-controller-manager static pod command (most authoritative) ── + // The --cluster-cidr flag is the actual configured pod CIDR, not a per-node value. + try + { + string args = await RunKubectlOutputAsync( + $"get pod -n kube-system -l component=kube-controller-manager -o jsonpath={{.items[0].spec.containers[0].command}} --kubeconfig={tempKubeconfig}", ct); + if (!string.IsNullOrWhiteSpace(args)) + { + Match m = Regex.Match(args, @"--cluster-cidr[=""'\s]+([0-9a-f.:/ ,]+?)(?:[""'\s]|$)"); + if (m.Success) podCidr = m.Groups[1].Value.Trim('"', '\'', ' ', ','); + } + } + catch { } + + // ── Source 2: kube-apiserver static pod command for service CIDR ── + try + { + string args = await RunKubectlOutputAsync( + $"get pod -n kube-system -l component=kube-apiserver -o jsonpath={{.items[0].spec.containers[0].command}} --kubeconfig={tempKubeconfig}", ct); + if (!string.IsNullOrWhiteSpace(args)) + { + Match m = Regex.Match(args, @"--service-cluster-ip-range[=""'\s]+([0-9a-f.:/ ,]+?)(?:[""'\s]|$)"); + if (m.Success) serviceCidr = m.Groups[1].Value.Trim('"', '\'', ' ', ','); + } + } + catch { } + + // ── Source 3: kubeadm-config ConfigMap (kubeadm clusters) ── + // Contains explicit networking.podSubnet and networking.serviceSubnet. + if (podCidr is null || serviceCidr is null) + { + try + { + string yaml = await RunKubectlOutputAsync( + $"get cm kubeadm-config -n kube-system -o jsonpath={{.data.ClusterConfiguration}} --kubeconfig={tempKubeconfig}", ct); + if (!string.IsNullOrWhiteSpace(yaml)) + { + if (podCidr is null) + { + Match m = Regex.Match(yaml, @"podSubnet:\s*([0-9a-f.:/ ]+)"); + if (m.Success) podCidr = m.Groups[1].Value.Trim(); + } + if (serviceCidr is null) + { + Match m = Regex.Match(yaml, @"serviceSubnet:\s*([0-9a-f.:/ ]+)"); + if (m.Success) serviceCidr = m.Groups[1].Value.Trim(); + } + } + } + catch { } + } + + // ── Source 4: kube-proxy ConfigMap clusterCIDR (fallback for pod CIDR) ── + if (podCidr is null) + { + try + { + string conf = await RunKubectlOutputAsync( + $"get cm kube-proxy -n kube-system -o jsonpath={{.data.config\\.conf}} --kubeconfig={tempKubeconfig}", ct); + Match m = Regex.Match(conf, @"clusterCIDR:\s*([0-9a-f.:/ ,]+)"); + if (m.Success) podCidr = m.Groups[1].Value.Trim(); + } + catch { } + } + + // ── Source 5: compute supernet from all node spec.podCIDR values ── + // This gives the minimum covering CIDR across all current nodes. + // Note: the computed supernet may be narrower than the configured cluster CIDR + // if not all nodes have been assigned subnets yet, but it's better than + // returning a single /24 that only covers one node. + if (podCidr is null) + { + try + { + string raw = await RunKubectlOutputAsync( + $"get nodes -o jsonpath={{.items[*].spec.podCIDR}} --kubeconfig={tempKubeconfig}", ct); + if (!string.IsNullOrWhiteSpace(raw)) + { + string[] nodeCidrs = raw.Split(' ', StringSplitOptions.RemoveEmptyEntries); + podCidr = ComputeIpv4Supernet(nodeCidrs); + } + } + catch { } + } + + // ── Source 6: k3s node annotation (k3s encodes its args on each node) ── + if (serviceCidr is null) + { + try + { + string raw = await RunKubectlOutputAsync( + $"get nodes -o jsonpath={{.items[0].metadata.annotations}} --kubeconfig={tempKubeconfig}", ct); + // k3s stores server args as a JSON-encoded string in an annotation + Match m = Regex.Match(raw, @"--service-cidr[=\s""\\]+([0-9./]+)"); + if (m.Success) serviceCidr = m.Groups[1].Value; + } + catch { } + } + + return (podCidr, serviceCidr); + } + finally + { + if (File.Exists(tempKubeconfig)) File.Delete(tempKubeconfig); + } + } + + /// + /// Computes the smallest IPv4 supernet that covers all given CIDRs. + /// e.g. ["10.244.0.0/24","10.244.1.0/24","10.244.2.0/24"] → "10.244.0.0/22" + /// + private static string? ComputeIpv4Supernet(string[] cidrs) + { + var networks = new List<(uint network, int prefix)>(); + foreach (string cidr in cidrs) + { + int slash = cidr.IndexOf('/'); + if (slash < 0) continue; + if (!System.Net.IPAddress.TryParse(cidr[..slash], out var addr)) continue; + if (!int.TryParse(cidr[(slash + 1)..], out int prefix)) continue; + byte[] b = addr.GetAddressBytes(); + if (b.Length != 4) continue; + uint ip = (uint)b[0] << 24 | (uint)b[1] << 16 | (uint)b[2] << 8 | b[3]; + uint mask = prefix > 0 ? ~((1u << (32 - prefix)) - 1) : 0u; + networks.Add((ip & mask, prefix)); + } + + if (networks.Count == 0) return null; + if (networks.Count == 1) + { + // Single node — widen the prefix by 8 bits as a heuristic + // (e.g. /24 per node → likely /16 cluster CIDR). + // The user can always adjust the value manually. + (uint net, int pfx) = networks[0]; + int widenedPfx = Math.Max(0, pfx - 8); + uint widenedMask = widenedPfx > 0 ? ~((1u << (32 - widenedPfx)) - 1) : 0u; + uint widenedNet = net & widenedMask; + return FormatCidr(widenedNet, widenedPfx); + } + + // Find the common bit prefix of all network addresses. + uint first = networks[0].network; + int commonBits = 32; + foreach ((uint net, _) in networks.Skip(1)) + { + uint diff = first ^ net; + if (diff == 0) continue; + commonBits = Math.Min(commonBits, 31 - (int)Math.Log2(diff)); + } + + uint superMask = commonBits > 0 ? ~((1u << (32 - commonBits)) - 1) : 0u; + return FormatCidr(first & superMask, commonBits); + } + + private static string FormatCidr(uint ip, int prefix) + => $"{ip >> 24}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}/{prefix}"; + + // ── Gateway component auto-link ─────────────────────────────────────────────── + + /// + /// For any local endpoint whose ComponentId is null, searches the cluster's + /// installed components for a matching gateway (strongswan or submariner) and + /// links it. Saves changes if any links are resolved. + /// Called lazily on each tunnel load so the UI reflects reality without + /// requiring manual wiring. + /// + public async Task ReconcileGatewayLinksAsync(Guid tenantId, CancellationToken ct = default) + { + using ApplicationDbContext db = dbFactory.CreateDbContext(); + + List unlinked = await db.VpnLocalEndpoints + .Where(e => e.ComponentId == null && e.Tunnel.TenantId == tenantId) + .Include(e => e.Tunnel) + .ToListAsync(ct); + + if (unlinked.Count == 0) return; + + bool changed = false; + foreach (VpnLocalEndpoint ep in unlinked) + { + // Names to search for, depending on tunnel type and endpoint role. + string[] candidateNames = ep.Tunnel.TunnelType == VpnTunnelType.SiteToSite + ? ["strongswan"] + : ep.Role == VpnEndpointRole.Gateway + ? ["submariner-broker", "submariner"] + : ["submariner", "submariner-gateway"]; + + ClusterComponent? match = await db.ClusterComponents + .Where(c => c.ClusterId == ep.ClusterId + && (candidateNames.Contains(c.Name) + || (c.ReleaseName != null && candidateNames.Contains(c.ReleaseName)))) + .OrderByDescending(c => c.InstalledAt) + .FirstOrDefaultAsync(ct); + + if (match is not null) + { + ep.ComponentId = match.Id; + ep.Status = match.Status == ComponentStatus.Installed + ? VpnEndpointStatus.Ready + : VpnEndpointStatus.Pending; + changed = true; + } + } + + if (changed) await db.SaveChangesAsync(ct); + } + + // ── Helpers ────────────────────────────────────────────────────────────────── + + public static string[] ParseSubnets(string subnetsJson) + => JsonSerializer.Deserialize(subnetsJson) ?? []; + + public static string SerializeSubnets(IEnumerable subnets) + => JsonSerializer.Serialize(subnets.ToArray()); + + private static string SanitizeConnectionName(string name) + => Regex.Replace(name.ToLowerInvariant(), "[^a-z0-9]", "-"); + + private static async Task RunKubectlOutputAsync(string arguments, CancellationToken ct) + { + using Process process = new() + { + StartInfo = new ProcessStartInfo + { + FileName = "kubectl", + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + process.Start(); + Task outTask = process.StandardOutput.ReadToEndAsync(ct); + await process.WaitForExitAsync(ct); + return await outTask; + } + + private static string BuildSyncManifest(string swanctlConf, string secretsB64) + { + // Indent each line by 4 spaces so it embeds cleanly as a YAML literal block scalar. + string escapedConf = string.Join("\n", swanctlConf.Split('\n').Select(l => " " + l)); + return $""" + apiVersion: v1 + kind: Namespace + metadata: + name: vpn-system + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: strongswan-config + namespace: vpn-system + data: + # Mounted at /etc/swanctl/conf.d/connections.conf via subPath + connections.conf: | + {escapedConf} + --- + apiVersion: v1 + kind: Secret + metadata: + name: strongswan-secrets + namespace: vpn-system + type: Opaque + data: + # Mounted at /etc/swanctl/conf.d/secrets.conf via subPath (PSK encrypted in vault). + secrets.conf: {secretsB64} + """; + } +} diff --git a/src/EntKube.Web/Services/YamlFormMerger.cs b/src/EntKube.Web/Services/YamlFormMerger.cs index b6b1a0f..9dc8dab 100644 --- a/src/EntKube.Web/Services/YamlFormMerger.cs +++ b/src/EntKube.Web/Services/YamlFormMerger.cs @@ -84,6 +84,32 @@ public class ComponentFormField /// installs both base and istiod) supply sensible defaults for each subchart's install. /// public string? SubchartDefaultValues { get; init; } + + /// + /// When true, this field is not rendered in the form UI. Hidden fields with + /// StoreAsSecret=true are used for credentials that are injected programmatically + /// (e.g. S3 credentials from a StorageLink, CNPG passwords from the vault) rather + /// than entered by the user. + /// + public bool Hidden { get; init; } + + /// + /// When true, the plaintext value is bcrypt-hashed (work factor 12) before being + /// written to the Kubernetes Secret during sync. Use for components like wg-easy + /// that require a bcrypt hash in their env vars (PASSWORD_HASH) rather than + /// the raw password. The vault always stores the original plaintext so it can be + /// revealed in the UI and re-hashed on subsequent syncs. + /// + public bool BcryptOnSync { get; init; } + + /// + /// A literal placeholder string in the manifest YAML (e.g. "%%WG_GATEWAY%%") that + /// is replaced with this field's vault secret value at apply-time. Used for + /// Manifest-type components where the value must appear verbatim in the YAML + /// (e.g. a gateway name in an EnvoyFilter workloadSelector) rather than being + /// injected via a YAML path. Only honoured when StoreAsSecret is also true. + /// + public string? ManifestPlaceholder { get; init; } } /// @@ -97,7 +123,16 @@ public enum FormFieldType Toggle, Select, CnpgDatabase, - ClusterIssuer + ClusterIssuer, + StorageLink, + /// + /// Renders as a dropdown of installed Istio gateway components on the cluster. + /// Selecting a gateway also auto-detects its LoadBalancer IP and writes it to + /// any sibling field whose Key is "wg-host". The stored value is the gateway's + /// Helm release name (e.g. "istio-ingress-external"), which is used for + /// ManifestPlaceholder substitution at apply-time. + /// + GatewaySelector } /// diff --git a/src/EntKube.Web/appsettings.Development.json b/src/EntKube.Web/appsettings.Development.json index 95ec744..c9b687f 100644 --- a/src/EntKube.Web/appsettings.Development.json +++ b/src/EntKube.Web/appsettings.Development.json @@ -11,5 +11,8 @@ }, "Vault": { "RootKey": "dGhpcyBpcyBhIDMyIGJ5dGUga2V5ISEhMTIzNDU2Nzg=" + }, + "Seed" : { + "AdminEmail": "nils.blomgren@gmail.com" } } diff --git a/src/EntKube.Web/wwwroot/app.css b/src/EntKube.Web/wwwroot/app.css index 9134483..4236a39 100644 --- a/src/EntKube.Web/wwwroot/app.css +++ b/src/EntKube.Web/wwwroot/app.css @@ -57,4 +57,14 @@ h1:focus { .form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { text-align: start; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.spin { + display: inline-block; + animation: spin 1s linear infinite; } \ No newline at end of file